diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000000..b8425e1f9d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Except this file !.gitignore +.classpath +.project +.settings +target +.project +.idea +.DS_Store +/logs +*.iml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000000..e4e74b6ec19 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,31 @@ +notifications: + email: + recipients: + - nacos_dev@linux.alibaba.com + - dev-nacos@googlegroups.com + on_success: change + on_failure: always + +language: java + +matrix: + include: + # On OSX, run with default JDK only. + # - os: osx + # On Linux, run with specific JDKs only. + - os: linux + env: CUSTOM_JDK="oraclejdk8" + +before_install: + - echo 'MAVEN_OPTS="$MAVEN_OPTS -Xmx1024m -XX:MaxPermSize=512m -XX:+BytecodeVerificationLocal"' >> ~/.mavenrc + - cat ~/.mavenrc + - if [ "$TRAVIS_OS_NAME" == "osx" ]; then export JAVA_HOME=$(/usr/libexec/java_home); fi + - if [ "$TRAVIS_OS_NAME" == "linux" ]; then jdk_switcher use "$CUSTOM_JDK"; fi + +script: + - travis_retry mvn -B clean apache-rat:check + - travis_retry mvn -B package jacoco:report coveralls:report + +after_success: + - mvn clean install -Pit-test + - mvn sonar:sonar -Psonar-apache diff --git a/BUILDING b/BUILDING new file mode 100644 index 00000000000..e7e968b8b45 --- /dev/null +++ b/BUILDING @@ -0,0 +1,38 @@ +Build Instructions for NACOS + +==================================================== + +(1) Prerequisites + + JDK 1.8+ is required in order to compile and run Nacos. + + nacos utilizes Maven as a distribution management and packaging tool. Version 3.0.3 or later is required. + Maven installation and configuration instructions can be found here: + + http://maven.apache.org/run-maven/index.html + + +(2) Run test cases + + Execute the following command in order to compile and run test cases of each components: + + $ mvn test + + +(3) Import projects to Eclipse IDE + + First, generate eclipse project files: + + $ mvn -U eclipse:eclipse + + Then, import to eclipse by specifying the root directory of the project via: + + [File] > [Import] > [Existing Projects into Workspace]. + + +(4) Build distribution packages + + Execute the following command in order to build the tar.gz packages and install JAR into local repository: + + #build nacos + $ mvn -Prelease-nacos -DskipTests clean install -U \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000000..e75d1970000 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,31 @@ +## How To Contribute + +We are always very happy to have contributions, whether for trivial cleanups or big new features. +We want to have high quality, well documented codes for each programming language. + +Nor is code the only way to contribute to the project. We strongly value documentation, integration with other project, and gladly accept improvements for these aspects. + +## Contributing code + +To submit a change for inclusion, please do the following: + +#### If the change is non-trivial please include some unit tests that cover the new functionality. +#### If you are introducing a completely new feature or API it is a good idea to start a wiki and get consensus on the basic design first. +#### It is our job to follow up on patches in a timely fashion. Nag us if we aren't doing our job (sometimes we drop things). + +## Becoming a Committer + +We are always interested in adding new contributors. What we look for are series of contributions, good taste and ongoing interest in the project. If you are interested in becoming a committer, please let one of the existing committers know and they can help you walk through the process. + +Nowadays,we have several important contribution points: +#### Wiki & JavaDoc +#### Nacos Console +#### Nacos SDK(C++\.Net\Php\Python\Go\Node.js) + +##### Prerequisite +If you want to contribute the above listing points, you must abide our some prerequisites: + +###### Readability - API must have Javadoc,some very important methods also must have javadoc +###### Testability - 80% above unit test coverage about main process +###### Maintainability - Comply with our [PMD spec](style/codeStyle.xml), and at least 3 month update frequency +###### Deployability - We encourage you to deploy into [maven repository](http://search.maven.org/) diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000000..7f77f44e739 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (properties) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. \ No newline at end of file diff --git a/NOTICE b/NOTICE new file mode 100644 index 00000000000..dcaaa07ecf4 --- /dev/null +++ b/NOTICE @@ -0,0 +1,32 @@ +Nacos +Copyright 2018-2019 The Apache Software Foundation + +This product includes software developed at +The Alibaba MiddleWare Group. + +------ +This product has a bundle Spring Boot: + The Spring Boot Project + ================= + +Please visit the Spring Boot web site for more information: + + * https://spring.io/projects/spring-boot + +Copyright 2014 The Spring Boot Project + +The Spring Boot Project 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. + +Also, please refer to each LICENSE..txt file, which is located in +the 'license' directory of the distribution file, for the license terms of the +components that this product depends on. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000000..3b3b20c911c --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +## Nacos + + +[![Gitter](https://badges.gitter.im/alibaba/nacos.svg)](https://gitter.im/alibaba/nacos?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) +[![Gitter](https://travis-ci.org/alibaba/nacos.svg?branch=master)](https://travis-ci.org/alibaba/nacos) + +------- + +Nacos is an easy-to-use platform desgined for dynamic service discovery and configuration and service management. It helps you to build cloud native applications and microservices platform easily. + +Service is a first-class citizen in Nacos. Nacos supports almost all type of services,for example,[Dubbo/gRPC service](xx)、[Spring Cloud RESTFul service](xx) or [Kubernetes service](xx). + +Nacos provides four major funcations. + +* **Service Discovery and Service Health Check** + + Nacos makes it simple for services to register themselves and to discover other services via a DNS or HTTP interface. Nacos also provides real-time healthchecks of services to prevent sending requests to unhealthy hosts or service instance. + +* **Dynamic Configuration Management** + + Dynamic Configuration Service allows you to manage configurations of all services in a centralized and dynamic manner across all environments. Nacos eliminates the need to redeploy applications and services when configurations are updated,which makes configuration changes more efficient and agile. + +* **Dynamic DNS Service** + + Nacos supports weighted routing, making it easier for you to implement mid-tier load balancing, flexible routing policies, flow control, and simple DNS resolution services in the production environment within your data center. It helps you to implement DNS-based service discovery easily and prevent applications from coupling to vendor-specific service discovery APIs. + +* **Service and MetaData Management** + + Nacos provides an easy-to-use service dashboard to help you manage your services metadata, configuration, kubernetes DNS, service health and metrics statistics. + + +## Quick Start (TODO) +It is super easy to get started with your first project. + +https://nacos.io/#/docs/quick-start.md + +Quick start for other open-source projects: + +[quick start with spring cloud](xx) +[quick start with dubbo](xx) +[quick start with kubernetes](xx) +[more...](xx) + + +## Documentation + +You can view full documentation on the Nacos website: + +[https://nacos.io/#/blog](https://nacos.io/#/blog) diff --git a/client/pom.xml b/client/pom.xml new file mode 100644 index 00000000000..78ddf9da055 --- /dev/null +++ b/client/pom.xml @@ -0,0 +1,107 @@ + + + com.alibaba.nacos + nacos-all + 0.1.0 + + + 4.0.0 + + nacos-client + jar + + nacos-client ${project.version} + http://maven.apache.org + + + UTF-8 + + + + + org.slf4j + slf4j-api + provided + + + log4j + log4j + provided + + + org.apache.logging.log4j + log4j-core + 2.8.2 + provided + + + commons-logging + commons-logging + provided + + + org.slf4j + slf4j-log4j12 + provided + + + org.apache.logging.log4j + log4j-slf4j-impl + provided + + + org.slf4j + jcl-over-slf4j + provided + + + junit + junit + test + + + ${project.groupId} + nacos-common + + + com.alibaba + fastjson + + + + ch.qos.logback + logback-classic + + + ch.qos.logback + logback-core + + + + com.google.guava + guava + + + + commons-codec + commons-codec + + + + org.codehaus.jackson + jackson-mapper-lgpl + + + net.jcip + jcip-annotations + true + + + com.github.spotbugs + spotbugs-annotations + true + + + + diff --git a/client/src/main/java/com/alibaba/nacos/api/NacosFactory.java b/client/src/main/java/com/alibaba/nacos/api/NacosFactory.java new file mode 100644 index 00000000000..747bc7a45b8 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/api/NacosFactory.java @@ -0,0 +1,86 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.api; + +import java.util.Properties; + +import com.alibaba.nacos.api.config.ConfigFactory; +import com.alibaba.nacos.api.config.ConfigService; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.naming.NamingFactory; +import com.alibaba.nacos.api.naming.NamingService; + +/** + * Nacos Factory + * + * @author Nacos + * + */ +public class NacosFactory { + + /** + * Create config + * + * @param properties + * init param + * @return config + * @throws NacosException + * Exception + */ + public static ConfigService createConfigService(Properties properties) throws NacosException { + return ConfigFactory.createConfigService(properties); + } + + /** + * Create config + * + * @param serverAddr + * server list + * @return config + * @throws NacosException + * Exception + */ + public static ConfigService createConfigService(String serverAddr) throws NacosException { + return ConfigFactory.createConfigService(serverAddr); + } + + /** + * Create Naming + * + * @param serverAddr + * server list + * @return Naming + * @throws NacosException + * Exception + */ + public static NamingService createNamingService(String serverAddr) throws NacosException { + return NamingFactory.createNamingService(serverAddr); + } + + /** + * Create Naming + * + * @param properties + * init param + * @return Naming + * @throws NacosException + * Exception + */ + public static NamingService createNamingService(Properties properties) throws NacosException { + return NamingFactory.createNamingService(properties); + } + +} diff --git a/client/src/main/java/com/alibaba/nacos/api/PropertyKeyConst.java b/client/src/main/java/com/alibaba/nacos/api/PropertyKeyConst.java new file mode 100644 index 00000000000..1fc3394301f --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/api/PropertyKeyConst.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.api; + +/** + * properties key + * @author Nacos + * + */ +public class PropertyKeyConst { + + public final static String ENDPOINT = "endpoint"; + public final static String NAMESPACE = "namespace"; + public final static String ACCESS_KEY = "accessKey"; + public final static String SECRET_KEY = "secretKey"; + public final static String SERVER_ADDR = "serverAddr"; + public final static String CONTEXT_PATH = "contextPath"; + public final static String CLUSTER_NAME = "clusterName"; + public final static String ENCODE = "encode"; + +} diff --git a/client/src/main/java/com/alibaba/nacos/api/config/ConfigFactory.java b/client/src/main/java/com/alibaba/nacos/api/config/ConfigFactory.java new file mode 100644 index 00000000000..9bfc7e7a4bd --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/api/config/ConfigFactory.java @@ -0,0 +1,60 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.api.config; + +import java.util.Properties; + +import com.alibaba.nacos.api.PropertyKeyConst; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.client.config.NacosConfigService; + +/** + * Config Factory + * + * @author Nacos + * + */ +public class ConfigFactory { + + /** + * Create Config + * + * @param properties + * init param + * @return Config + * @throws NacosException + * Exception + */ + public static ConfigService createConfigService(Properties properties) throws NacosException { + return new NacosConfigService(properties); + } + + /** + * Create Config + * + * @param ServerAddr + * serverlist + * @return Config + * @throws NacosException + * Exception + */ + public static ConfigService createConfigService(String serverAddr) throws NacosException { + Properties properties = new Properties(); + properties.put(PropertyKeyConst.SERVER_ADDR, serverAddr); + return new NacosConfigService(properties); + } + +} diff --git a/client/src/main/java/com/alibaba/nacos/api/config/ConfigService.java b/client/src/main/java/com/alibaba/nacos/api/config/ConfigService.java new file mode 100644 index 00000000000..9a96a528bef --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/api/config/ConfigService.java @@ -0,0 +1,103 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.api.config; + +import com.alibaba.nacos.api.config.listener.Listener; +import com.alibaba.nacos.api.exception.NacosException; + +/** + * Config Interface + * + * @author Nacos + * + */ +public interface ConfigService { + + /** + * Get Configuration + * + * @param dataId + * Config ID + * @param group + * Config Group + * @param timeoutMs + * read timeout + * @return config value + * @throws NacosException + * NacosException + */ + public String getConfig(String dataId, String group, long timeoutMs) throws NacosException; + + /** + * Add a listener to the configuration, after the server to modify the + * configuration, the client will use the incoming listener callback. + * Recommended asynchronous processing, the application can implement the + * getExecutor method in the ManagerListener, provide a thread pool of + * execution. If provided, use the main thread callback, May block other + * configurations or be blocked by other configurations. + * + * @param dataId + * Config ID + * @param group + * Config Group + * @param listener + * listener + * @throws NacosException + * NacosException + */ + public void addListener(String dataId, String group, Listener listener) throws NacosException; + + /** + * publish config. + * + * @param dataId + * Config ID + * @param group + * Config Group + * @param content + * Config Content + * @return Whether publish + * @throws NacosException + * NacosException + */ + public boolean publishConfig(String dataId, String group, String content) throws NacosException; + + /** + * Remove Config + * + * @param dataId + * Config ID + * @param group + * Config Group + * @return whether remove + * @throws NacosException + * NacosException + */ + public boolean removeConfig(String dataId, String group) throws NacosException; + + /** + * Remove Listener + * + * @param dataId + * Config ID + * @param group + * Config Group + * @param listener + * listener + */ + public void removeListener(String dataId, String group, Listener listener); + +} diff --git a/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigContext.java b/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigContext.java new file mode 100644 index 00000000000..2b26df8e4ca --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigContext.java @@ -0,0 +1,42 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.api.config.filter; + +/** + * Config Context Interface + * + * @author Nacos + * + */ +public interface IConfigContext { + /** + * get context by key + * + * @param key + * @return context + */ + public Object getParameter(String key); + + /** + * set context + * + * @param key + * key + * @param value + * value + */ + public void setParameter(String key, Object value); +} diff --git a/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigFilter.java b/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigFilter.java new file mode 100644 index 00000000000..e3007640808 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigFilter.java @@ -0,0 +1,69 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.api.config.filter; + +import com.alibaba.nacos.api.exception.NacosException; + +/** + * Config Filter Interface + * + * @author Nacos + * + */ +public interface IConfigFilter { + /** + * Init Fuction + * + * @param filterConfig + * Filter Config + */ + void init(IFilterConfig filterConfig); + + /** + * do filter + * + * @param request + * request + * @param response + * response + * @param filterChain + * filter Chain + * @throws NacosException + * exception + */ + void doFilter(IConfigRequest request, IConfigResponse response, IConfigFilterChain filterChain) + throws NacosException; + + /** + * deploy + */ + void deploy(); + + /** + * order + * + * @return + */ + int getOrder(); + + /** + * filterName + * + * @return + */ + String getFilterName(); + +} diff --git a/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigFilterChain.java b/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigFilterChain.java new file mode 100644 index 00000000000..ac084d67e49 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigFilterChain.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.api.config.filter; + +import com.alibaba.nacos.api.exception.NacosException; + +/** + * Config Filter Chain Interface + * + * @author Nacos + * + */ +public interface IConfigFilterChain { + /** + * Filter aciton + * + * @param request + * request + * @param response + * response + * @throws NacosException + * NacosException + */ + public void doFilter(IConfigRequest request, IConfigResponse response) throws NacosException; + +} diff --git a/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigRequest.java b/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigRequest.java new file mode 100644 index 00000000000..308fe06a6a8 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigRequest.java @@ -0,0 +1,41 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.api.config.filter; + +/** + * Config Request Interface + * + * @author Nacos + * + */ +public interface IConfigRequest { + /** + * get param + * + * @param key + * key + * @return value + */ + public Object getParameter(String key); + + /** + * get config context + * + * @return + */ + public IConfigContext getConfigContext(); + +} diff --git a/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigResponse.java b/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigResponse.java new file mode 100644 index 00000000000..69b5aca07e6 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigResponse.java @@ -0,0 +1,41 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.api.config.filter; + +/** + * Config Response Interface + * + * @author Nacos + * + */ +public interface IConfigResponse { + /** + * get param + * + * @param key + * key + * @return value + */ + public Object getParameter(String key); + + /** + * get context + * + * @return configContext + */ + public IConfigContext getConfigContext(); + +} diff --git a/client/src/main/java/com/alibaba/nacos/api/config/filter/IFilterConfig.java b/client/src/main/java/com/alibaba/nacos/api/config/filter/IFilterConfig.java new file mode 100644 index 00000000000..55a931ba843 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/api/config/filter/IFilterConfig.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.api.config.filter; +/** + * Filter Config Interface + * @author Nacos + * + */ +public interface IFilterConfig { + + /** + * get filter name + * + * @return + */ + public String getFilterName(); + + /** + * get param + * + * @param name + * @return param + */ + public Object getInitParameter(String name); + +} diff --git a/client/src/main/java/com/alibaba/nacos/api/config/listener/AbstractListener.java b/client/src/main/java/com/alibaba/nacos/api/config/listener/AbstractListener.java new file mode 100644 index 00000000000..817e0c5c566 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/api/config/listener/AbstractListener.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.api.config.listener; + +import java.util.concurrent.Executor; + +/** + * Listner Adapter,use default notify thread + * + * @author water.lyl + * + */ +@SuppressWarnings("PMD.AbstractClassShouldStartWithAbstractNamingRule") +public abstract class AbstractListener implements Listener { + + /** + * use default Executor + */ + public Executor getExecutor() { + return null; + } + +} diff --git a/client/src/main/java/com/alibaba/nacos/api/config/listener/AbstractSharedListener.java b/client/src/main/java/com/alibaba/nacos/api/config/listener/AbstractSharedListener.java new file mode 100644 index 00000000000..dd0f83dee6d --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/api/config/listener/AbstractSharedListener.java @@ -0,0 +1,57 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.api.config.listener; + +import java.util.concurrent.Executor; + +/** + * shared listener + * + * @author Nacos + * + */ +@SuppressWarnings("PMD.AbstractClassShouldStartWithAbstractNamingRule") +public abstract class AbstractSharedListener implements Listener { + private volatile String dataId; + private volatile String group; + + final public void fillContext(String dataId, String group) { + this.dataId = dataId; + this.group = group; + } + + @Override + final public void receiveConfigInfo(String configInfo) { + innerReceive(dataId, group, configInfo); + } + + @Override + public Executor getExecutor() { + return null; + } + + /** + * receive + * + * @param dataId + * data ID + * @param group + * group + * @param configInfo + * content + */ + public abstract void innerReceive(String dataId, String group, String configInfo); +} diff --git a/client/src/main/java/com/alibaba/nacos/api/config/listener/Listener.java b/client/src/main/java/com/alibaba/nacos/api/config/listener/Listener.java new file mode 100644 index 00000000000..f8dc734e801 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/api/config/listener/Listener.java @@ -0,0 +1,43 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.api.config.listener; + +import java.util.concurrent.Executor; + + +/** + * Listener for watch config + * + * @author Nacos + * + */ +public interface Listener { + + /** + * Executor to excute this receive + * + * @return Executor + */ + public Executor getExecutor(); + + + /** + * 接收配置信息 + * + * @param configInfo 配置值 + */ + public void receiveConfigInfo(final String configInfo); +} diff --git a/client/src/main/java/com/alibaba/nacos/api/exception/NacosException.java b/client/src/main/java/com/alibaba/nacos/api/exception/NacosException.java new file mode 100644 index 00000000000..7c5ffecd83a --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/api/exception/NacosException.java @@ -0,0 +1,110 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.api.exception; + +/** + * Nacos Exception + * + * @author Nacos + * + */ +public class NacosException extends Exception{ + + /** + * serialVersionUID + */ + private static final long serialVersionUID = -3913902031489277776L; + + private int errCode; + + private String errMsg; + + public NacosException() { + }; + + public NacosException(int errCode, String errMsg) { + this.errCode = errCode; + this.errMsg = errMsg; + } + + public int getErrCode() { + return errCode; + } + + public String getErrMsg() { + return errMsg; + } + + public void setErrCode(int errCode) { + this.errCode = errCode; + } + + public void setErrMsg(String errMsg) { + this.errMsg = errMsg; + } + + @Override + public String toString() { + return "ErrCode:" + errCode + ",ErrMsg:" + errMsg; + } + + /** + * client error code + * -400 -503 throw exception to user + */ + /** + * invalid param(参数错误) + */ + public static final int CLIENT_INVALID_PARAM = -400; + /** + * over client threshold(超过server端的限流阈值) + */ + public static final int CLIENT_OVER_THRESHOLD = -503; + + + /** + * server error code + * 400 403 throw exception to user + * 500 502 503 change ip and retry + */ + + /** + * invalid param(参数错误) + */ + public static final int INVALID_PARAM = 400; + /** + * no right(鉴权失败) + */ + public static final int NO_RIGHT = 403; + /** + * conflict(写并发冲突) + */ + public static final int CONFLICT = 409; + /** + * server error(server异常,如超时) + */ + public static final int SERVER_ERROR = 500; + /** + * bad gateway(路由异常,如nginx后面的Server挂掉) + */ + public static final int BAD_GATEWAY = 502; + /** + * over threshold(超过server端的限流阈值) + */ + public static final int OVER_THRESHOLD = 503; + + +} diff --git a/client/src/main/java/com/alibaba/nacos/api/naming/NamingFactory.java b/client/src/main/java/com/alibaba/nacos/api/naming/NamingFactory.java new file mode 100644 index 00000000000..2607f05d7b9 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/api/naming/NamingFactory.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.api.naming; + +import java.util.Properties; + +import com.alibaba.nacos.client.naming.NacosNamingService; + +/** + * @author dungu.zpf + */ +public class NamingFactory { + + public static NamingService createNamingService(String serverList) { + return new NacosNamingService(serverList); + } + + public static NamingService createNamingService(Properties properties) { + return new NacosNamingService(properties); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/api/naming/NamingService.java b/client/src/main/java/com/alibaba/nacos/api/naming/NamingService.java new file mode 100644 index 00000000000..a8f9763ae99 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/api/naming/NamingService.java @@ -0,0 +1,176 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.api.naming; + +import java.util.List; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.naming.listener.EventListener; +import com.alibaba.nacos.api.naming.pojo.Instance; + +/** + * @author dungu.zpf + */ +public interface NamingService { + + /** + * Register a instance to service + * + * @param serviceName name of service + * @param ip instance ip + * @param port instance port + * @throws NacosException + */ + void registerInstance(String serviceName, String ip, int port) throws NacosException; + + /** + * Register a instance to service with specified cluster name + * + * @param serviceName name of service + * @param ip instance ip + * @param port instance port + * @param clusterName instance cluster name + * @throws NacosException + */ + void registerInstance(String serviceName, String ip, int port, String clusterName) throws NacosException; + + /** + * Register a instance to service with specified instance properties + * + * @param serviceName name of service + * @param instance instance to register + * @throws NacosException + */ + void registerInstance(String serviceName, Instance instance) throws NacosException; + + /** + * Deregister instance from a service + * + * @param serviceName name of service + * @param ip instance ip + * @param port instance port + * @throws NacosException + */ + void deregisterInstance(String serviceName, String ip, int port) throws NacosException; + + /** + * Deregister instance with specified cluster name from a service + * + * @param serviceName name of service + * @param ip instance ip + * @param port instance port + * @param clusterName instance cluster name + * @throws NacosException + */ + void deregisterInstance(String serviceName, String ip, int port, String clusterName) throws NacosException; + + /** + * Get all instances of a service + * + * @param serviceName name of service + * @return A list of instance + * @throws NacosException + */ + List getAllInstances(String serviceName) throws NacosException; + + /** + * Get all instances within specified clusters of a service + * + * @param serviceName name of service + * @param clusters list of cluster + * @return A list of qualified instance + * @throws NacosException + */ + List getAllInstances(String serviceName, List clusters) throws NacosException; + + /** + * Get qualified instances of service + * + * @param serviceName name of service + * @param healthy a flag to indicate returning healthy or unhealthy instances + * @return A qualified list of instance + * @throws NacosException + */ + List selectInstances(String serviceName, boolean healthy) throws NacosException; + + /** + * Get qualified instances within specified clusters of service + * + * @param serviceName name of service + * @param clusters list of cluster + * @param healthy a flag to indicate returning healthy or unhealthy instances + * @return A qualified list of instance + * @throws NacosException + */ + List selectInstances(String serviceName, List clusters, boolean healthy) throws NacosException; + + /** + * Select one healthy instance of service using predefined load balance strategy + * + * @param serviceName name of service + * @return qualified instance + * @throws NacosException + */ + Instance selectOneHealthyInstance(String serviceName) throws NacosException; + + /** + * Select one healthy instance of service using predefined load balance strategy + * + * @param serviceName name of service + * @param clusters a list of clusters should the instance belongs to + * @return qualified instance + * @throws NacosException + */ + Instance selectOneHealthyInstance(String serviceName, List clusters) throws NacosException; + + /** + * Subscribe service to receive events of instances alteration + * + * @param serviceName name of service + * @param listener event listener + * @throws NacosException + */ + void subscribe(String serviceName, EventListener listener) throws NacosException; + + /** + * Subscribe service to receive events of instances alteration + * + * @param serviceName name of service + * @param clusters list of cluster + * @param listener event listener + * @throws NacosException + */ + void subscribe(String serviceName, List clusters, EventListener listener) throws NacosException; + + /** + * Unsubscribe event listener of service + * + * @param serviceName name of service + * @param listener event listener + * @throws NacosException + */ + void unsubscribe(String serviceName, EventListener listener) throws NacosException; + + /** + * Unsubscribe event listener of service + * + * @param serviceName name of service + * @param clusters list of cluster + * @param listener event listener + * @throws NacosException + */ + void unsubscribe(String serviceName, List clusters, EventListener listener) throws NacosException; +} diff --git a/client/src/main/java/com/alibaba/nacos/api/naming/listener/Event.java b/client/src/main/java/com/alibaba/nacos/api/naming/listener/Event.java new file mode 100644 index 00000000000..224d1ce60a2 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/api/naming/listener/Event.java @@ -0,0 +1,23 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.api.naming.listener; + +/** + * event interface + * @author dungu.zpf + */ +public interface Event { +} diff --git a/client/src/main/java/com/alibaba/nacos/api/naming/listener/EventListener.java b/client/src/main/java/com/alibaba/nacos/api/naming/listener/EventListener.java new file mode 100644 index 00000000000..b0f0b94201c --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/api/naming/listener/EventListener.java @@ -0,0 +1,29 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.api.naming.listener; + +/** + * event listener + * @author Nacos + * + */ +public interface EventListener { + /** + * callback event + * @param event + */ + void onEvent(Event event); +} diff --git a/client/src/main/java/com/alibaba/nacos/api/naming/listener/NamingEvent.java b/client/src/main/java/com/alibaba/nacos/api/naming/listener/NamingEvent.java new file mode 100644 index 00000000000..94b42a95463 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/api/naming/listener/NamingEvent.java @@ -0,0 +1,51 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.api.naming.listener; + +import java.util.List; + +import com.alibaba.nacos.api.naming.pojo.Instance; + +/** + * @author dungu.zpf + */ +public class NamingEvent implements Event { + + private String serviceName; + + private List instances; + + public NamingEvent(String serviceName, List instances) { + this.serviceName = serviceName; + this.instances = instances; + } + + public String getServiceName() { + return serviceName; + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + public List getInstances() { + return instances; + } + + public void setInstances(List instances) { + this.instances = instances; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/api/naming/pojo/AbstractHealthChecker.java b/client/src/main/java/com/alibaba/nacos/api/naming/pojo/AbstractHealthChecker.java new file mode 100644 index 00000000000..f6a46e57ded --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/api/naming/pojo/AbstractHealthChecker.java @@ -0,0 +1,179 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.api.naming.pojo; + +import com.alibaba.nacos.client.naming.utils.StringUtils; + +import java.util.Objects; + +/** + * @author dungu.zpf + */ +public abstract class AbstractHealthChecker implements Cloneable { + + protected String type = "unknown"; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public static class Http extends AbstractHealthChecker { + public static final String TYPE = "HTTP"; + + private String path = StringUtils.EMPTY; + private String headers = StringUtils.EMPTY; + + private int expectedResponseCode = 200; + + public Http() { + this.type = TYPE; + } + + public int getExpectedResponseCode() { + return expectedResponseCode; + } + + public void setExpectedResponseCode(int expectedResponseCode) { + this.expectedResponseCode = expectedResponseCode; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getHeaders() { + return headers; + } + + public void setHeaders(String headers) { + this.headers = headers; + } + + @Override + public int hashCode() { + return Objects.hash(path, headers, expectedResponseCode); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Http)) { + return false; + } + + Http other = (Http) obj; + + if (!StringUtils.equals(type, other.getType())) { + return false; + } + + if (!StringUtils.equals(path, other.getPath())) { + return false; + } + if (!StringUtils.equals(headers, other.getHeaders())) { + return false; + } + return expectedResponseCode == other.getExpectedResponseCode(); + } + } + + public static class Tcp extends AbstractHealthChecker { + public static final String TYPE = "TCP"; + + public Tcp() { + this.type = TYPE; + } + + @Override + public int hashCode() { + return Objects.hash(TYPE); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof Tcp; + + } + } + + public static class Mysql extends AbstractHealthChecker { + public static final String TYPE = "MYSQL"; + + private String user; + private String pwd; + private String cmd; + + public Mysql() { + this.type = TYPE; + } + + public String getCmd() { + return cmd; + } + + public String getPwd() { + return pwd; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public void setCmd(String cmd) { + this.cmd = cmd; + } + + public void setPwd(String pwd) { + this.pwd = pwd; + } + + @Override + public int hashCode() { + return Objects.hash(user, pwd, cmd); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Mysql)) { + return false; + } + + Mysql other = (Mysql) obj; + + if (!StringUtils.equals(user, other.getUser())) { + return false; + } + + if (!StringUtils.equals(pwd, other.getPwd())) { + return false; + } + + return StringUtils.equals(cmd, other.getCmd()); + + } + } +} diff --git a/client/src/main/java/com/alibaba/nacos/api/naming/pojo/Cluster.java b/client/src/main/java/com/alibaba/nacos/api/naming/pojo/Cluster.java new file mode 100644 index 00000000000..eec4a3e1b05 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/api/naming/pojo/Cluster.java @@ -0,0 +1,124 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.api.naming.pojo; + +import com.alibaba.nacos.client.naming.utils.StringUtils; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author dungu.zpf + */ +public class Cluster { + + /** + * Name of belonging service + */ + private String serviceName; + + /** + * Name of cluster + */ + private String name = StringUtils.EMPTY; + + /** + * Health check config of this cluster + */ + private AbstractHealthChecker healthChecker = new AbstractHealthChecker.Tcp(); + + /** + * Default registered port for instances in this cluster. + */ + private int defaultPort = 80; + + /** + * Default health check port of instances in this cluster. + */ + private int defaultCheckPort = 80; + + /** + * Whether or not use instance port to do health check. + */ + private boolean useIPPort4Check = true; + + + private Map metadata = new HashMap<>(); + + public Cluster() { + + } + + public Cluster(String clusterName) { + this.name = clusterName; + } + + public String getServiceName() { + return serviceName; + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public AbstractHealthChecker getHealthChecker() { + return healthChecker; + } + + public void setHealthChecker(AbstractHealthChecker healthChecker) { + this.healthChecker = healthChecker; + } + + public int getDefaultPort() { + return defaultPort; + } + + public void setDefaultPort(int defaultPort) { + this.defaultPort = defaultPort; + } + + public int getDefaultCheckPort() { + return defaultCheckPort; + } + + public void setDefaultCheckPort(int defaultCheckPort) { + this.defaultCheckPort = defaultCheckPort; + } + + public boolean isUseIPPort4Check() { + return useIPPort4Check; + } + + public void setUseIPPort4Check(boolean useIPPort4Check) { + this.useIPPort4Check = useIPPort4Check; + } + + public Map getMetadata() { + return metadata; + } + + public void setMetadata(Map metadata) { + this.metadata = metadata; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/api/naming/pojo/Instance.java b/client/src/main/java/com/alibaba/nacos/api/naming/pojo/Instance.java new file mode 100644 index 00000000000..55ecfaa044d --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/api/naming/pojo/Instance.java @@ -0,0 +1,166 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.api.naming.pojo; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.annotation.JSONField; +import com.alibaba.nacos.client.naming.utils.StringUtils; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author dungu.zpf + */ +public class Instance { + + /** + * Unique ID of this instance. + */ + private String instanceId; + + /** + * Instance ip + */ + private String ip; + + /** + * Instance port + */ + private int port; + + /** + * Instance weight + */ + private double weight = 1.0D; + + /** + * Instance health status + */ + @JSONField(name = "valid") + private boolean healthy = true; + + /** + * Cluster information of instance + */ + @JSONField(serialize = false) + private Cluster cluster = new Cluster(); + + /** + * Service information of instance + */ + @JSONField(serialize = false) + private Service service; + + /** + * User extended attributes + */ + private Map metadata = new HashMap<>(); + + public String getInstanceId() { + return instanceId; + } + + public void setInstanceId(String instanceId) { + this.instanceId = instanceId; + } + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public double getWeight() { + return weight; + } + + public void setWeight(double weight) { + this.weight = weight; + } + + public boolean isHealthy() { + return healthy; + } + + public void setHealthy(boolean healthy) { + this.healthy = healthy; + } + + public Cluster getCluster() { + return cluster; + } + + public void setCluster(Cluster cluster) { + this.cluster = cluster; + } + + public Service getService() { + return service; + } + + public void setService(Service service) { + this.service = service; + } + + public Map getMetadata() { + return metadata; + } + + public void setMetadata(Map metadata) { + this.metadata = metadata; + } + + public void addMetadata(String key, String value) { + this.metadata.put(key, value); + } + + @Override + public String toString() { + return JSON.toJSONString(this); + } + + public String toInetAddr() { + return ip + ":" + port; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Instance)) { + return false; + } + + Instance host = (Instance) obj; + + return StringUtils.equals(toString(), host.toString()); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + +} diff --git a/client/src/main/java/com/alibaba/nacos/api/naming/pojo/Service.java b/client/src/main/java/com/alibaba/nacos/api/naming/pojo/Service.java new file mode 100644 index 00000000000..e5274f1cb2d --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/api/naming/pojo/Service.java @@ -0,0 +1,108 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.api.naming.pojo; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author dungu.zpf + */ +public class Service { + + /** + * Service name + */ + private String name; + + /** + * Protect threshold + */ + private float protectThreshold = 0.0F; + + /** + * Application name of this service + */ + private String app; + + /** + * Service group which is meant to classify services into different sets. + */ + private String group; + + /** + * Health check mode. + */ + private String healthCheckMode; + + public Service(String name) { + this.name = name; + } + + private Map metadata = new HashMap<>(); + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getHealthCheckMode() { + return healthCheckMode; + } + + public void setHealthCheckMode(String healthCheckMode) { + this.healthCheckMode = healthCheckMode; + } + + public float getProtectThreshold() { + return protectThreshold; + } + + public void setProtectThreshold(float protectThreshold) { + this.protectThreshold = protectThreshold; + } + + public String getApp() { + return app; + } + + public void setApp(String app) { + this.app = app; + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + public Map getMetadata() { + return metadata; + } + + public void setMetadata(Map metadata) { + this.metadata = metadata; + } + + public void addMetadata(String key, String value) { + this.metadata.put(key, value); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/NacosConfigService.java b/client/src/main/java/com/alibaba/nacos/client/config/NacosConfigService.java new file mode 100644 index 00000000000..3b4953e1039 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/NacosConfigService.java @@ -0,0 +1,267 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.config; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Properties; + +import com.alibaba.nacos.api.PropertyKeyConst; +import com.alibaba.nacos.api.config.ConfigService; +import com.alibaba.nacos.api.config.listener.Listener; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.client.config.common.Constants; +import com.alibaba.nacos.client.config.filter.impl.ConfigFilterChainManager; +import com.alibaba.nacos.client.config.filter.impl.ConfigRequest; +import com.alibaba.nacos.client.config.filter.impl.ConfigResponse; +import com.alibaba.nacos.client.config.impl.ClientWorker; +import com.alibaba.nacos.client.config.impl.LocalConfigInfoProcessor; +import com.alibaba.nacos.client.config.impl.ServerHttpAgent; +import com.alibaba.nacos.client.config.impl.HttpSimpleClient.HttpResult; +import com.alibaba.nacos.client.config.utils.ContentUtils; +import com.alibaba.nacos.client.config.utils.LogUtils; +import com.alibaba.nacos.client.config.utils.ParamUtils; +import com.alibaba.nacos.client.config.utils.TenantUtil; +import com.alibaba.nacos.client.logger.Logger; +import com.alibaba.nacos.client.logger.support.LoggerHelper; +import com.alibaba.nacos.client.utils.StringUtils; + +/** + * Config Impl + * @author Nacos + * + */ +@SuppressWarnings("PMD.ServiceOrDaoClassShouldEndWithImplRule") +public class NacosConfigService implements ConfigService { + + final static public Logger log = LogUtils.logger(NacosConfigService.class); + public final long POST_TIMEOUT = 3000L; + /** + * http agent + */ + private ServerHttpAgent agent; + /** + * longpulling + */ + private ClientWorker worker; + private String namespace; + private String encode; + private ConfigFilterChainManager configFilterChainManager = new ConfigFilterChainManager(); + + public NacosConfigService(Properties properties) throws NacosException { + String encodeTmp = properties.getProperty(PropertyKeyConst.ENCODE); + if (StringUtils.isBlank(encodeTmp)) { + encode = Constants.ENCODE; + } else { + encode = encodeTmp.trim(); + } + String namespaceTmp = properties.getProperty(PropertyKeyConst.NAMESPACE); + if (StringUtils.isBlank(namespaceTmp)) { + namespace = TenantUtil.getUserTenant(); + properties.put(PropertyKeyConst.NAMESPACE, namespace); + } else { + namespace = namespaceTmp; + properties.put(PropertyKeyConst.NAMESPACE, namespace); + } + agent = new ServerHttpAgent(properties); + agent.start(); + worker = new ClientWorker(agent, configFilterChainManager); + } + + @Override + public String getConfig(String dataId, String group, long timeoutMs) throws NacosException { + return getConfigInner(namespace, dataId, group, timeoutMs); + } + + @Override + public void addListener(String dataId, String group, Listener listener) throws NacosException { + worker.addTenantListeners(dataId, group, Arrays.asList(listener)); + } + + @Override + public boolean publishConfig(String dataId, String group, String content) throws NacosException { + return publishConfigInner(namespace, dataId, group, null, null, null, content); + } + + @Override + public boolean removeConfig(String dataId, String group) throws NacosException { + return removeConfigInner(namespace, dataId, group, null); + } + + @Override + public void removeListener(String dataId, String group, Listener listener) { + worker.removeTenantListener(dataId, group, listener); + } + + private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException { + group = null2defaultGroup(group); + ParamUtils.checkKeyParam(dataId, group); + ConfigResponse cr = new ConfigResponse(); + + cr.setDataId(dataId); + cr.setTenant(tenant); + cr.setGroup(group); + + // 优先使用本地配置 + String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant); + if (content != null) { + log.warn(agent.getName(), "[get-config] get failover ok, dataId={}, group={}, tenant={}, config={}", dataId, + group, tenant, ContentUtils.truncateContent(content)); + cr.setContent(content); + configFilterChainManager.doFilter(null, cr); + content = cr.getContent(); + return content; + } + + try { + content = worker.getServerConfig(dataId, group, tenant, timeoutMs); + cr.setContent(content); + configFilterChainManager.doFilter(null, cr); + content = cr.getContent(); + return content; + } catch (NacosException ioe) { + if (NacosException.NO_RIGHT == ioe.getErrCode()) { + throw ioe; + } + log.warn("NACOS-0003", + LoggerHelper.getErrorCodeStr("NACOS", "NACOS-0003", "环境问题", "get from server error")); + log.warn(agent.getName(), "[get-config] get from server error, dataId={}, group={}, tenant={}, msg={}", + dataId, group, tenant, ioe.toString()); + } + + log.warn(agent.getName(), "[get-config] get snapshot ok, dataId={}, group={}, tenant={}, config={}", dataId, + group, tenant, ContentUtils.truncateContent(content)); + content = LocalConfigInfoProcessor.getSnapshot(agent.getName(), dataId, group, tenant); + cr.setContent(content); + configFilterChainManager.doFilter(null, cr); + content = cr.getContent(); + return content; + } + + private String null2defaultGroup(String group) { + return (null == group) ? Constants.DEFAULT_GROUP : group.trim(); + } + + private boolean removeConfigInner(String tenant, String dataId, String group, String tag) throws NacosException { + group = null2defaultGroup(group); + ParamUtils.checkKeyParam(dataId, group); + String url = Constants.CONFIG_CONTROLLER_PATH; + List params = new ArrayList(); + params.add("dataId"); + params.add(dataId); + params.add("group"); + params.add(group); + if (StringUtils.isNotEmpty(tenant)) { + params.add("tenant"); + params.add(tenant); + } + if (StringUtils.isNotEmpty(tag)) { + params.add("tag"); + params.add(tag); + } + HttpResult result = null; + try { + result = agent.httpDelete(url, null, params, encode, POST_TIMEOUT); + } catch (IOException ioe) { + log.warn("[remove] error, " + dataId + ", " + group + ", " + tenant + ", msg: " + ioe.toString()); + return false; + } + + if (HttpURLConnection.HTTP_OK == result.code) { + log.info(agent.getName(), "[remove] ok, dataId={}, group={}, tenant={}", dataId, group, tenant); + return true; + } else if (HttpURLConnection.HTTP_FORBIDDEN == result.code) { + log.warn(agent.getName(), "[remove] error, dataId={}, group={}, tenant={}, code={}, msg={}", dataId, group, + tenant, result.code, result.content); + throw new NacosException(result.code, result.content); + } else { + log.warn(agent.getName(), "[remove] error, dataId={}, group={}, tenant={}, code={}, msg={}", dataId, group, + tenant, result.code, result.content); + return false; + } + } + + private boolean publishConfigInner(String tenant, String dataId, String group, String tag, String appName, + String betaIps, String content) throws NacosException { + group = null2defaultGroup(group); + ParamUtils.checkParam(dataId, group, content); + + ConfigRequest cr = new ConfigRequest(); + cr.setDataId(dataId); + cr.setTenant(tenant); + cr.setGroup(group); + cr.setContent(content); + configFilterChainManager.doFilter(cr, null); + content = cr.getContent(); + + String url = Constants.CONFIG_CONTROLLER_PATH; + List params = new ArrayList(); + params.add("dataId"); + params.add(dataId); + params.add("group"); + params.add(group); + params.add("content"); + params.add(content); + if (StringUtils.isNotEmpty(tenant)) { + params.add("tenant"); + params.add(tenant); + } + if (StringUtils.isNotEmpty(appName)) { + params.add("appName"); + params.add(appName); + } + if (StringUtils.isNotEmpty(tag)) { + params.add("tag"); + params.add(tag); + } + + List headers = new ArrayList(); + if (StringUtils.isNotEmpty(betaIps)) { + headers.add("betaIps"); + headers.add(betaIps); + } + + HttpResult result = null; + try { + result = agent.httpPost(url, headers, params, encode, POST_TIMEOUT); + } catch (IOException ioe) { + log.warn("NACOS-0006", + LoggerHelper.getErrorCodeStr("NACOS", "NACOS-0006", "环境问题", "[publish-single] exception")); + log.warn(agent.getName(), "[publish-single] exception, dataId={}, group={}, msg={}", dataId, group, + ioe.toString()); + return false; + } + + if (HttpURLConnection.HTTP_OK == result.code) { + log.info(agent.getName(), "[publish-single] ok, dataId={}, group={}, tenant={}, config={}", dataId, group, + tenant, ContentUtils.truncateContent(content)); + return true; + } else if (HttpURLConnection.HTTP_FORBIDDEN == result.code) { + log.warn(agent.getName(), "[publish-single] error, dataId={}, group={}, tenant={}, code={}, msg={}", dataId, + group, tenant, result.code, result.content); + throw new NacosException(result.code, result.content); + } else { + log.warn(agent.getName(), "[publish-single] error, dataId={}, group={}, tenant={}, code={}, msg={}", dataId, + group, tenant, result.code, result.content); + return false; + } + + } + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/common/Constants.java b/client/src/main/java/com/alibaba/nacos/client/config/common/Constants.java new file mode 100644 index 00000000000..6fe35f92315 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/common/Constants.java @@ -0,0 +1,127 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.config.common; + +/** + * Constant + * + * @author Nacos + * + */ +public class Constants { + + public static final String CLIENT_VERSION_HEADER = "Client-Version"; + + public static final String CLIENT_VERSION = "3.0.0"; + + public static int DATA_IN_BODY_VERSION = 204; + + public static final String DEFAULT_GROUP = "DEFAULT_GROUP"; + + public static final String APPNAME = "AppName"; + + public static final String UNKNOWN_APP = "UnknownApp"; + + public static final String DEFAULT_DOMAINNAME = "commonconfig.config-host.taobao.com"; + + public static final String DAILY_DOMAINNAME = "commonconfig.taobao.net"; + + public static final int DEFAULT_PORT = 8080; + + public static final String NULL = ""; + + public static final String DATAID = "dataId"; + + public static final String GROUP = "group"; + + public static final String LAST_MODIFIED = "Last-Modified"; + + public static final String ACCEPT_ENCODING = "Accept-Encoding"; + + public static final String CONTENT_ENCODING = "Content-Encoding"; + + public static final String PROBE_MODIFY_REQUEST = "Listening-Configs"; + + public static final String PROBE_MODIFY_RESPONSE = "Probe-Modify-Response"; + + public static final String PROBE_MODIFY_RESPONSE_NEW = "Probe-Modify-Response-New"; + + public static final String USE_ZIP = "true"; + + public static final String CONTENT_MD5 = "Content-MD5"; + + public static final String CONFIG_VERSION = "Config-Version"; + + public static final String IF_MODIFIED_SINCE = "If-Modified-Since"; + + public static final String SPACING_INTERVAL = "client-spacing-interval"; + + public static final String BASE_PATH = "/v1/cs"; + + public static final String CONFIG_CONTROLLER_PATH = BASE_PATH + "/configs"; + + /** + * second + */ + public static final int ASYNC_UPDATE_ADDRESS_INTERVAL = 300; + + /** + * second + */ + public static final int POLLING_INTERVAL_TIME = 15; + + /** + * millisecond + */ + public static final int ONCE_TIMEOUT = 2000; + + /** + * millisecond + */ + public static final int CONN_TIMEOUT = 2000; + + /** + * millisecond + */ + public static final int SO_TIMEOUT = 60000; + + /** + * millisecond + */ + public static final int RECV_WAIT_TIMEOUT = ONCE_TIMEOUT * 5; + + public static final String ENCODE = "UTF-8"; + + public static final String MAP_FILE = "map-file.js"; + + public static final int FLOW_CONTROL_THRESHOLD = 20; + + public static final int FLOW_CONTROL_SLOT = 10; + + public static final int FLOW_CONTROL_INTERVAL = 1000; + + public static final String LINE_SEPARATOR = Character.toString((char) 1); + + public static final String WORD_SEPARATOR = Character.toString((char) 2); + + public static final String LONGPULLING_LINE_SEPARATOR = "\r\n"; + + public static final String CLIENT_APPNAME_HEADER = "Client-AppName"; + public static final String CLIENT_REQUEST_TS_HEADER = "Client-RequestTS"; + public static final String CLIENT_REQUEST_TOKEN_HEADER = "Client-RequestToken"; + + public static final int ATOMIC_MAX_SIZE = 1000; +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/common/GroupKey.java b/client/src/main/java/com/alibaba/nacos/client/config/common/GroupKey.java new file mode 100644 index 00000000000..fce61eddc38 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/common/GroupKey.java @@ -0,0 +1,123 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.config.common; + +import com.alibaba.nacos.client.utils.StringUtils; + +/** + * Synthesize the form of dataId+groupId. Escapes reserved characters in dataId + * and groupId. + * + * @author Nacos + */ +public class GroupKey { + + static public String getKey(String dataId, String group) { + StringBuilder sb = new StringBuilder(); + urlEncode(dataId, sb); + sb.append('+'); + urlEncode(group, sb); + return sb.toString(); + } + + static public String getKeyTenant(String dataId, String group, String tenant) { + StringBuilder sb = new StringBuilder(); + urlEncode(dataId, sb); + sb.append('+'); + urlEncode(group, sb); + if (StringUtils.isNotEmpty(tenant)) { + sb.append('+'); + urlEncode(tenant, sb); + } + return sb.toString(); + } + + static public String getKey(String dataId, String group, String datumStr) { + StringBuilder sb = new StringBuilder(); + urlEncode(dataId, sb); + sb.append('+'); + urlEncode(group, sb); + sb.append('+'); + urlEncode(datumStr, sb); + return sb.toString(); + } + + static public String[] parseKey(String groupKey) { + StringBuilder sb = new StringBuilder(); + String dataId = null; + String group = null; + String tenant = null; + + for (int i = 0; i < groupKey.length(); ++i) { + char c = groupKey.charAt(i); + if ('+' == c) { + if (null == dataId) { + dataId = sb.toString(); + sb.setLength(0); + } else if (null == group) { + group = sb.toString(); + sb.setLength(0); + } else { + throw new IllegalArgumentException("invalid groupkey:" + groupKey); + } + } else if ('%' == c) { + char next = groupKey.charAt(++i); + char nextnext = groupKey.charAt(++i); + if ('2' == next && 'B' == nextnext) { + sb.append('+'); + } else if ('2' == next && '5' == nextnext) { + sb.append('%'); + } else { + throw new IllegalArgumentException("invalid groupkey:" + groupKey); + } + } else { + sb.append(c); + } + } + + if (StringUtils.isBlank(group)) { + group = sb.toString(); + if (group.length() == 0) { + throw new IllegalArgumentException("invalid groupkey:" + groupKey); + } + } else { + tenant = sb.toString(); + if (group.length() == 0) { + throw new IllegalArgumentException("invalid groupkey:" + groupKey); + } + } + + return new String[] { dataId, group, tenant }; + } + + /** + * + -> %2B + * % -> %25 + */ + static void urlEncode(String str, StringBuilder sb) { + for (int idx = 0; idx < str.length(); ++idx) { + char c = str.charAt(idx); + if ('+' == c) { + sb.append("%2B"); + } else if ('%' == c) { + sb.append("%25"); + } else { + sb.append(c); + } + } + } + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/filter/impl/ConfigContext.java b/client/src/main/java/com/alibaba/nacos/client/config/filter/impl/ConfigContext.java new file mode 100644 index 00000000000..23c65f6ec3e --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/filter/impl/ConfigContext.java @@ -0,0 +1,43 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.config.filter.impl; + +import java.util.HashMap; +import java.util.Map; + +import com.alibaba.nacos.api.config.filter.IConfigContext; + +/** + * Config Context + * + * @author Nacos + * + */ +public class ConfigContext implements IConfigContext { + + private Map param = new HashMap(); + + @Override + public Object getParameter(String key) { + return param.get(key); + } + + @Override + public void setParameter(String key, Object value) { + param.put(key, value); + } + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/filter/impl/ConfigFilterChainManager.java b/client/src/main/java/com/alibaba/nacos/client/config/filter/impl/ConfigFilterChainManager.java new file mode 100644 index 00000000000..1566113ed80 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/filter/impl/ConfigFilterChainManager.java @@ -0,0 +1,87 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.config.filter.impl; + +import java.util.List; + +import com.alibaba.nacos.api.config.filter.IConfigFilter; +import com.alibaba.nacos.api.config.filter.IConfigFilterChain; +import com.alibaba.nacos.api.config.filter.IConfigRequest; +import com.alibaba.nacos.api.config.filter.IConfigResponse; +import com.alibaba.nacos.api.exception.NacosException; +import com.google.common.collect.Lists; + +/** + * Config Filter Chain Management + * + * @author Nacos + * + */ +public class ConfigFilterChainManager implements IConfigFilterChain { + + private List filters = Lists.newArrayList(); + + public synchronized ConfigFilterChainManager addFilter(IConfigFilter filter) { + // 根据order大小顺序插入 + int i = 0; + while (i < this.filters.size()) { + IConfigFilter currentValue = this.filters.get(i); + if (currentValue.getFilterName().equals(filter.getFilterName())) { + break; + } + if (filter.getOrder() >= currentValue.getOrder() && i < this.filters.size()) { + i++; + } else { + this.filters.add(i, filter); + break; + } + } + + if (i == this.filters.size()) { + this.filters.add(i, filter); + } + return this; + } + + + @Override + public void doFilter(IConfigRequest request, IConfigResponse response) throws NacosException { + new VirtualFilterChain(this.filters).doFilter(request, response); + } + + private static class VirtualFilterChain implements IConfigFilterChain { + + private final List additionalFilters; + + private int currentPosition = 0; + + public VirtualFilterChain(List additionalFilters) { + this.additionalFilters = additionalFilters; + } + + @Override + public void doFilter(final IConfigRequest request, final IConfigResponse response) throws NacosException { + if (this.currentPosition == this.additionalFilters.size()) { + return; + } else { + this.currentPosition++; + IConfigFilter nextFilter = this.additionalFilters.get(this.currentPosition - 1); + nextFilter.doFilter(request, response, this); + } + } + } + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/filter/impl/ConfigRequest.java b/client/src/main/java/com/alibaba/nacos/client/config/filter/impl/ConfigRequest.java new file mode 100644 index 00000000000..100b10d39c2 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/filter/impl/ConfigRequest.java @@ -0,0 +1,78 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.config.filter.impl; + +import java.util.HashMap; +import java.util.Map; + +import com.alibaba.nacos.api.config.filter.IConfigContext; +import com.alibaba.nacos.api.config.filter.IConfigRequest; + +/** + * Config Request + * + * @author Nacos + * + */ +public class ConfigRequest implements IConfigRequest { + + private Map param = new HashMap(); + + private IConfigContext configContext = new ConfigContext(); + + public String getTenant() { + return (String) param.get("tenant"); + } + + public void setTenant(String tenant) { + param.put("tenant", tenant); + } + + public String getDataId() { + return (String) param.get("dataId"); + } + + public void setDataId(String dataId) { + param.put("dataId", dataId); + } + + public String getGroup() { + return (String) param.get("group"); + } + + public void setGroup(String group) { + param.put("group", group); + } + + public String getContent() { + return (String) param.get("content"); + } + + public void setContent(String content) { + param.put("content", content); + } + + @Override + public Object getParameter(String key) { + return param.get(key); + } + + @Override + public IConfigContext getConfigContext() { + return configContext; + } + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/filter/impl/ConfigResponse.java b/client/src/main/java/com/alibaba/nacos/client/config/filter/impl/ConfigResponse.java new file mode 100644 index 00000000000..c864331cc6b --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/filter/impl/ConfigResponse.java @@ -0,0 +1,78 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.config.filter.impl; + +import java.util.HashMap; +import java.util.Map; + +import com.alibaba.nacos.api.config.filter.IConfigContext; +import com.alibaba.nacos.api.config.filter.IConfigResponse; + +/** + * Config Response + * + * @author Nacos + * + */ +public class ConfigResponse implements IConfigResponse { + + private Map param = new HashMap(); + + private IConfigContext configContext = new ConfigContext(); + + public String getTenant() { + return (String) param.get("tenant"); + } + + public void setTenant(String tenant) { + param.put("tenant", tenant); + } + + public String getDataId() { + return (String) param.get("dataId"); + } + + public void setDataId(String dataId) { + param.put("dataId", dataId); + } + + public String getGroup() { + return (String) param.get("group"); + } + + public void setGroup(String group) { + param.put("group", group); + } + + public String getContent() { + return (String) param.get("content"); + } + + public void setContent(String content) { + param.put("content", content); + } + + @Override + public Object getParameter(String key) { + return param.get(key); + } + + @Override + public IConfigContext getConfigContext() { + return configContext; + } + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/impl/CacheData.java b/client/src/main/java/com/alibaba/nacos/client/config/impl/CacheData.java new file mode 100644 index 00000000000..008c810205c --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/impl/CacheData.java @@ -0,0 +1,319 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.config.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import com.alibaba.nacos.api.config.listener.AbstractSharedListener; +import com.alibaba.nacos.api.config.listener.Listener; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.client.config.common.Constants; +import com.alibaba.nacos.client.config.filter.impl.ConfigFilterChainManager; +import com.alibaba.nacos.client.config.filter.impl.ConfigResponse; +import com.alibaba.nacos.client.config.utils.LogUtils; +import com.alibaba.nacos.client.config.utils.MD5; +import com.alibaba.nacos.client.config.utils.TenantUtil; +import com.alibaba.nacos.client.logger.Logger; + +/** + * Listner Management + * + * @author Nacos + * + */ +public class CacheData { + + final static public Logger log = LogUtils.logger(CacheData.class); + + public boolean isInitializing() { + return isInitializing; + } + + public void setInitializing(boolean isInitializing) { + this.isInitializing = isInitializing; + } + + public String getMd5() { + return md5; + } + + public String getTenant() { + return tenant; + } + + public String getContent() { + return content; + } + + public void setContent(String newContent) { + this.content = newContent; + this.md5 = getMd5String(content); + } + + /** + * Add listener + * + * @param listener + * listener + */ + public void addListener(Listener listener) { + if (null == listener) { + throw new IllegalArgumentException("listener is null"); + } + ManagerListenerWrap wrap = new ManagerListenerWrap(listener); + if (listeners.addIfAbsent(wrap)) { + log.info(name, "[add-listener] ok, tenant={}, dataId={}, group={}, cnt={}", tenant, dataId, group, + listeners.size()); + } + } + + public void removeListener(Listener listener) { + if (null == listener) { + throw new IllegalArgumentException("listener is null"); + } + ManagerListenerWrap wrap = new ManagerListenerWrap(listener); + if (listeners.remove(wrap)) { + log.info(name, "[remove-listener] ok, dataId={}, group={}, cnt={}", dataId, group, listeners.size()); + } + } + + /** + * 返回监听器列表上的迭代器,只读。保证不返回NULL。 + */ + public List getListeners() { + List result = new ArrayList(); + for (ManagerListenerWrap wrap : listeners) { + result.add(wrap.listener); + } + return result; + } + + + public long getLocalConfigInfoVersion() { + return localConfigLastModified; + } + public void setLocalConfigInfoVersion(long localConfigLastModified) { + this.localConfigLastModified = localConfigLastModified; + } + + + public boolean isUseLocalConfigInfo() { + return isUseLocalConfig; + } + public void setUseLocalConfigInfo(boolean useLocalConfigInfo) { + this.isUseLocalConfig = useLocalConfigInfo; + if (!useLocalConfigInfo) { + localConfigLastModified = -1; + } + } + + public int getTaskId() { + return taskId; + } + + public void setTaskId(int taskId) { + this.taskId = taskId; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((dataId == null) ? 0 : dataId.hashCode()); + result = prime * result + ((group == null) ? 0 : group.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (null == obj || obj.getClass() != getClass()) { + return false; + } + if (this == obj) { + return true; + } + CacheData other = (CacheData) obj; + return dataId.equals(other.dataId) && group.equals(other.group); + } + + @Override + public String toString() { + return "CacheData [" + dataId + ", " + group + "]"; + } + + void checkListenerMd5() { + for (ManagerListenerWrap wrap : listeners) { + if (!md5.equals(wrap.lastCallMd5)) { + safeNotifyListener(dataId, group, content, md5, wrap); + } + } + } + + private void safeNotifyListener(final String dataId, final String group, final String content, + final String md5, final ManagerListenerWrap listenerWrap) { + final Listener listener = listenerWrap.listener; + + Runnable job = new Runnable() { + public void run() { + ClassLoader myClassLoader = Thread.currentThread().getContextClassLoader(); + ClassLoader appClassLoader= listener.getClass().getClassLoader(); + try { + if(listener instanceof AbstractSharedListener){ + AbstractSharedListener adapter = (AbstractSharedListener) listener; + adapter.fillContext(dataId, group); + log.info(name, "[notify-context] dataId={}, group={}, md5={}", dataId, group, md5); + } + // 执行回调之前先将线程classloader设置为具体webapp的classloader,以免回调方法中调用spi接口是出现异常或错用(多应用部署才会有该问题)。 + Thread.currentThread().setContextClassLoader(appClassLoader); + + ConfigResponse cr = new ConfigResponse(); + cr.setDataId(dataId); + cr.setGroup(group); + cr.setContent(content); + configFilterChainManager.doFilter(null, cr); + String contentTmp = cr.getContent(); + listener.receiveConfigInfo(contentTmp); + listenerWrap.lastCallMd5 = md5; + log.info( + name, + "[notify-ok] dataId={}, group={}, md5={}, listener={} ", + dataId, group, md5, listener); + } catch (NacosException de) { + log.error(name, "NACOS-XXXX", + "[notify-error] dataId={}, group={}, md5={}, listener={} errCode={} errMsg={}", dataId, + group, md5, listener, de.getErrCode(), de.getErrMsg()); + } catch (Throwable t) { + log.error(name, "NACOS-XXXX", + "[notify-error] dataId={}, group={}, md5={}, listener={} tx={}", dataId, group, md5, + listener, t.getCause()); + } + finally + { + Thread.currentThread().setContextClassLoader(myClassLoader); + } + } + }; + + final long startNotify = System.currentTimeMillis(); + try { + if (null != listener.getExecutor()) { + listener.getExecutor().execute(job); + } else { + job.run(); + } + } catch (Throwable t) { + log.error( + name, + "NACOS-XXXX", + "[notify-error] dataId={}, group={}, md5={}, listener={} throwable={}", + dataId, group, md5, listener, t.getCause()); + } + final long finishNotify = System.currentTimeMillis(); + log.info(name, "[notify-listener] time cost={}ms in ClientWorker, dataId={}, group={}, md5={}, listener={} ",(finishNotify - startNotify), dataId ,group, md5, listener); + } + + static public String getMd5String(String config) { + return (null == config) ? Constants.NULL : MD5.getInstance().getMD5String(config); + } + + private String loadCacheContentFromDiskLocal(String name, String dataId, String group, String tenant) { + String content = LocalConfigInfoProcessor.getFailover(name, dataId, group, tenant); + content = (null != content) ? content + : LocalConfigInfoProcessor.getSnapshot(name, dataId, group, tenant); + return content; + } + + public CacheData(ConfigFilterChainManager configFilterChainManager, String name, String dataId, String group) { + if (null == dataId || null == group) { + throw new IllegalArgumentException("dataId=" + dataId + ", group=" + group); + } + this.name = name; + this.configFilterChainManager = configFilterChainManager; + this.dataId = dataId; + this.group = group; + this.tenant = TenantUtil.getUserTenant(); + listeners = new CopyOnWriteArrayList(); + this.isInitializing = true; + this.content = loadCacheContentFromDiskLocal(name, dataId, group, tenant); + this.md5 = getMd5String(content); + } + + public CacheData(ConfigFilterChainManager configFilterChainManager, String name, String dataId, String group, String tenant) { + if (null == dataId || null == group) { + throw new IllegalArgumentException("dataId=" + dataId + ", group=" + group); + } + this.name = name; + this.configFilterChainManager = configFilterChainManager; + this.dataId = dataId; + this.group = group; + this.tenant = tenant; + listeners = new CopyOnWriteArrayList(); + this.isInitializing = true; + this.content = loadCacheContentFromDiskLocal(name, dataId, group, tenant); + this.md5 = getMd5String(content); + } + + // ================== + + private final String name; + private final ConfigFilterChainManager configFilterChainManager; + public final String dataId; + public final String group; + public final String tenant; + private final CopyOnWriteArrayList listeners; + + private volatile String md5; + /** + * whether use local config + */ + private volatile boolean isUseLocalConfig = false; + /** + * last motify time + */ + private volatile long localConfigLastModified; + private volatile String content; + private int taskId; + private volatile boolean isInitializing = true; +} + +class ManagerListenerWrap { + final Listener listener; + String lastCallMd5 = CacheData.getMd5String(null); + + ManagerListenerWrap(Listener listener) { + this.listener = listener; + } + + @Override + public boolean equals(Object obj) { + if (null == obj || obj.getClass() != getClass()) { + return false; + } + if (obj == this) { + return true; + } + ManagerListenerWrap other = (ManagerListenerWrap) obj; + return listener.equals(other.listener); + } + + @Override + public int hashCode() { + return super.hashCode(); + } + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/impl/ClientWorker.java b/client/src/main/java/com/alibaba/nacos/client/config/impl/ClientWorker.java new file mode 100644 index 00000000000..5f60a81fc51 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/impl/ClientWorker.java @@ -0,0 +1,541 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.config.impl; + +import static com.alibaba.nacos.client.config.common.Constants.LINE_SEPARATOR; +import static com.alibaba.nacos.client.config.common.Constants.WORD_SEPARATOR; + +import java.io.File; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import com.alibaba.nacos.api.config.listener.Listener; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.client.config.common.Constants; +import com.alibaba.nacos.client.config.common.GroupKey; +import com.alibaba.nacos.client.config.filter.impl.ConfigFilterChainManager; +import com.alibaba.nacos.client.config.impl.HttpSimpleClient.HttpResult; +import com.alibaba.nacos.client.config.utils.ContentUtils; +import com.alibaba.nacos.client.config.utils.LogUtils; +import com.alibaba.nacos.client.config.utils.MD5; +import com.alibaba.nacos.client.config.utils.TenantUtil; +import com.alibaba.nacos.client.logger.Logger; +import com.alibaba.nacos.client.logger.support.LoggerHelper; +import com.alibaba.nacos.client.utils.ParamUtil; +import com.alibaba.nacos.client.utils.StringUtils; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +/** + * Longpulling + * + * @author Nacos + * + */ +public class ClientWorker { + + final static public Logger log = LogUtils.logger(ClientWorker.class); + + public void addListeners(String dataId, String group, List listeners) { + group = null2defaultGroup(group); + CacheData cache = addCacheDataIfAbsent(dataId, group); + for (Listener listener : listeners) { + cache.addListener(listener); + } + } + + public void removeListener(String dataId, String group, Listener listener) { + group = null2defaultGroup(group); + CacheData cache = getCache(dataId, group); + if (null != cache) { + cache.removeListener(listener); + if (cache.getListeners().isEmpty()) { + removeCache(dataId, group); + } + } + } + + public void addTenantListeners(String dataId, String group, List listeners) { + group = null2defaultGroup(group); + String tenant = agent.getTenant(); + CacheData cache = addCacheDataIfAbsent(dataId, group, tenant); + for (Listener listener : listeners) { + cache.addListener(listener); + } + } + + public void removeTenantListener(String dataId, String group, Listener listener) { + group = null2defaultGroup(group); + String tenant = agent.getTenant(); + CacheData cache = getCache(dataId, group, tenant); + if (null != cache) { + cache.removeListener(listener); + if (cache.getListeners().isEmpty()) { + removeCache(dataId, group, tenant); + } + } + } + + @SuppressFBWarnings("JLM_JSR166_UTILCONCURRENT_MONITORENTER") + void removeCache(String dataId, String group) { + String groupKey = GroupKey.getKey(dataId, group); + synchronized (cacheMap) { + Map copy = new HashMap(cacheMap.get()); + copy.remove(groupKey); + cacheMap.set(copy); + } + log.info(agent.getName(), "[unsubscribe] {}", groupKey); + } + + @SuppressFBWarnings("JLM_JSR166_UTILCONCURRENT_MONITORENTER") + void removeCache(String dataId, String group, String tenant) { + String groupKey = GroupKey.getKeyTenant(dataId, group, tenant); + synchronized (cacheMap) { + Map copy = new HashMap(cacheMap.get()); + copy.remove(groupKey); + cacheMap.set(copy); + } + log.info(agent.getName(), "[unsubscribe] {}", groupKey); + } + + @SuppressFBWarnings("JLM_JSR166_UTILCONCURRENT_MONITORENTER") + public CacheData addCacheDataIfAbsent(String dataId, String group) { + CacheData cache = getCache(dataId, group); + if (null != cache) { + return cache; + } + + String key = GroupKey.getKey(dataId, group); + cache = new CacheData(configFilterChainManager, agent.getName(), dataId, group); + + synchronized (cacheMap) { + CacheData cacheFromMap = getCache(dataId, group); + // multiple listeners on the same dataid+group and race condition,so double check again + //other listener thread beat me to set to cacheMap + if(null != cacheFromMap) { + cache = cacheFromMap; + //reset so that server not hang this check + cache.setInitializing(true); + } else { + int taskId = cacheMap.get().size() / (int) ParamUtil.getPerTaskConfigSize(); + cache.setTaskId(taskId); + } + + Map copy = new HashMap(cacheMap.get()); + copy.put(key, cache); + cacheMap.set(copy); + } + + log.info(agent.getName(), "[subscribe] {}", key); + + return cache; + } + @SuppressFBWarnings("JLM_JSR166_UTILCONCURRENT_MONITORENTER") + public CacheData addCacheDataIfAbsent(String dataId, String group, String tenant) { + CacheData cache = getCache(dataId, group, tenant); + if (null != cache) { + return cache; + } + String key = GroupKey.getKeyTenant(dataId, group, tenant); + cache = new CacheData(configFilterChainManager, agent.getName(), dataId, group, tenant); + synchronized (cacheMap) { + CacheData cacheFromMap = getCache(dataId, group, tenant); + // multiple listeners on the same dataid+group and race condition,so + // double check again + // other listener thread beat me to set to cacheMap + if (null != cacheFromMap) { + cache = cacheFromMap; + // reset so that server not hang this check + cache.setInitializing(true); + } + + Map copy = new HashMap(cacheMap.get()); + copy.put(key, cache); + cacheMap.set(copy); + } + log.info(agent.getName(), "[subscribe] {}", key); + return cache; + } + + public CacheData getCache(String dataId, String group) { + return getCache(dataId, group, TenantUtil.getUserTenant()); + } + + public CacheData getCache(String dataId, String group, String tenant) { + if (null == dataId || null == group) { + throw new IllegalArgumentException(); + } + return cacheMap.get().get(GroupKey.getKeyTenant(dataId, group, tenant)); + } + + + public String getServerConfig(String dataId, String group, String tenant, long readTimeout) + throws NacosException { + if (StringUtils.isBlank(group)) { + group = Constants.DEFAULT_GROUP; + } + + HttpResult result = null; + try { + List params = null; + if (StringUtils.isBlank(tenant)) { + params = Arrays.asList("dataId", dataId, "group", group); + } else { + params = Arrays.asList("dataId", dataId, "group", group, "tenant", tenant); + } + result = agent.httpGet(Constants.CONFIG_CONTROLLER_PATH, null, params, agent.getEncode(), readTimeout); + } catch (IOException e) { + log.error(agent.getName(), "NACOS-XXXX", + "[sub-server] get server config exception, dataId={}, group={}, tenant={}, msg={}", dataId, group, + tenant, e.toString()); + throw new NacosException(NacosException.SERVER_ERROR, e.getMessage()); + } + + switch (result.code) { + case HttpURLConnection.HTTP_OK: + LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, result.content); + return result.content; + case HttpURLConnection.HTTP_NOT_FOUND: + LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, null); + return null; + case HttpURLConnection.HTTP_CONFLICT: { + log.error(agent.getName(), "NACOS-XXXX", + "[sub-server-error] get server config being modified concurrently, dataId={}, group={}, tenant={}", + dataId, group, tenant); + throw new NacosException(NacosException.CONFLICT, + "data being modified, dataId=" + dataId + ",group=" + group + ",tenant=" + tenant); + } + case HttpURLConnection.HTTP_FORBIDDEN: { + log.error(agent.getName(), "NACOS-XXXX", "[sub-server-error] no right, dataId={}, group={}, tenant={}", + dataId, group, tenant); + throw new NacosException(result.code, result.content); + } + default: { + log.error(agent.getName(), "NACOS-XXXX", "[sub-server-error] dataId={}, group={}, tenant={}, code={}", + dataId, group, tenant, result.code); + throw new NacosException(result.code, + "http error, code=" + result.code + ",dataId=" + dataId + ",group=" + group + ",tenant=" + tenant); + } + } + } + + private void checkLocalConfig(CacheData cacheData) { + final String dataId = cacheData.dataId; + final String group = cacheData.group; + final String tenant = cacheData.tenant; + File path = LocalConfigInfoProcessor.getFailoverFile(agent.getName(), dataId, group, tenant); + + // 没有 -> 有 + if (!cacheData.isUseLocalConfigInfo() && path.exists()) { + String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant); + String md5 = MD5.getInstance().getMD5String(content); + cacheData.setUseLocalConfigInfo(true); + cacheData.setLocalConfigInfoVersion(path.lastModified()); + cacheData.setContent(content); + + log.warn(agent.getName(), + "[failover-change] failover file created. dataId={}, group={}, tenant={}, md5={}, content={}", + dataId, group, tenant, md5, ContentUtils.truncateContent(content)); + return; + } + + // 有 -> 没有。不通知业务监听器,从server拿到配置后通知。 + if (cacheData.isUseLocalConfigInfo() && !path.exists()) { + cacheData.setUseLocalConfigInfo(false); + log.warn(agent.getName(), "[failover-change] failover file deleted. dataId={}, group={}, tenant={}", dataId, + group, tenant); + return; + } + + // 有变更 + if (cacheData.isUseLocalConfigInfo() && path.exists() + && cacheData.getLocalConfigInfoVersion() != path.lastModified()) { + String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant); + String md5 = MD5.getInstance().getMD5String(content); + cacheData.setUseLocalConfigInfo(true); + cacheData.setLocalConfigInfoVersion(path.lastModified()); + cacheData.setContent(content); + log.warn(agent.getName(), + "[failover-change] failover file changed. dataId={}, group={}, tenant={}, md5={}, content={}", + dataId, group, tenant, md5, ContentUtils.truncateContent(content)); + return; + } + } + + private String null2defaultGroup(String group) { + return (null == group) ? Constants.DEFAULT_GROUP : group.trim(); + } + + public void checkConfigInfo() { + // 分任务 + int listenerSize = cacheMap.get().size(); + // 向上取整为批数 + int longingTaskCount = (int) Math.ceil(listenerSize / ParamUtil.getPerTaskConfigSize()); + if (longingTaskCount > currentLongingTaskCount) { + for (int i = (int) currentLongingTaskCount; i < longingTaskCount; i++) { + // 要判断任务是否在执行 这块需要好好想想。 任务列表现在是无序的。变化过程可能有问题 + executorService.execute(new LongPullingRunnable(i)); + } + currentLongingTaskCount = longingTaskCount; + } + } + + /** + * 从Server获取值变化了的DataID列表。返回的对象里只有dataId和group是有效的。 保证不返回NULL。 + */ + List checkUpdateDataIds(List cacheDatas, List inInitializingCacheList) { + StringBuilder sb = new StringBuilder(); + for (CacheData cacheData : cacheDatas) { + if (!cacheData.isUseLocalConfigInfo()) { + sb.append(cacheData.dataId).append(WORD_SEPARATOR); + sb.append(cacheData.group).append(WORD_SEPARATOR); + if (StringUtils.isBlank(cacheData.tenant)) { + sb.append(cacheData.getMd5()).append(LINE_SEPARATOR); + } else { + sb.append(cacheData.getMd5()).append(WORD_SEPARATOR); + sb.append(cacheData.getTenant()).append(LINE_SEPARATOR); + } + if (cacheData.isInitializing()) { + // cacheData 首次出现在cacheMap中&首次check更新 + inInitializingCacheList + .add(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant)); + } + } + } + boolean isInitializingCacheList = !inInitializingCacheList.isEmpty(); + return checkUpdateConfigStr(sb.toString(), isInitializingCacheList); + } + + /** + * 从Server获取值变化了的DataID列表。返回的对象里只有dataId和group是有效的。 保证不返回NULL。 + */ + List checkUpdateConfigStr(String probeUpdateString, boolean isInitializingCacheList) { + + List params = Arrays.asList(Constants.PROBE_MODIFY_REQUEST, probeUpdateString); + long timeout = TimeUnit.SECONDS.toMillis(30L); + + List headers = new ArrayList(2); + headers.add("Long-Pulling-Timeout"); + headers.add("" + timeout); + + // told server do not hang me up if new initializing cacheData added in + if (isInitializingCacheList) { + headers.add("Long-Pulling-Timeout-No-Hangup"); + headers.add("true"); + } + + if (StringUtils.isBlank(probeUpdateString)) { + return Collections.emptyList(); + } + + try { + HttpResult result = agent.httpPost(Constants.CONFIG_CONTROLLER_PATH + "/listener", headers, params, + agent.getEncode(), timeout); + + if (HttpURLConnection.HTTP_OK == result.code) { + setHealthServer(true); + return parseUpdateDataIdResponse(result.content); + } else { + setHealthServer(false); + if (result.code == HttpURLConnection.HTTP_INTERNAL_ERROR) { + log.error("NACOS-0007", LoggerHelper.getErrorCodeStr("Nacos", "Nacos-0007", "环境问题", + "[check-update] get changed dataId error")); + } + log.error(agent.getName(), "NACOS-XXXX", "[check-update] get changed dataId error, code={}", + result.code); + } + } catch (IOException e) { + setHealthServer(false); + log.error(agent.getName(), "NACOS-XXXX", "[check-update] get changed dataId exception, msg={}", + e.toString()); + } + return Collections.emptyList(); + } + + /** + * 从HTTP响应拿到变化的groupKey。保证不返回NULL。 + */ + private List parseUpdateDataIdResponse(String response) { + if (StringUtils.isBlank(response)) { + return Collections.emptyList(); + } + + try { + response = URLDecoder.decode(response, "UTF-8"); + } catch (Exception e) { + log.error(agent.getName(), "NACOS-XXXX", "[polling-resp] decode modifiedDataIdsString error", e); + } + + List updateList = new LinkedList(); + + for (String dataIdAndGroup : response.split(LINE_SEPARATOR)) { + if (!StringUtils.isBlank(dataIdAndGroup)) { + String[] keyArr = dataIdAndGroup.split(WORD_SEPARATOR); + String dataId = keyArr[0]; + String group = keyArr[1]; + if (keyArr.length == 2) { + updateList.add(GroupKey.getKey(dataId, group)); + log.info(agent.getName(), "[polling-resp] config changed. dataId={}, group={}", dataId, group); + } else if (keyArr.length == 3) { + String tenant = keyArr[2]; + updateList.add(GroupKey.getKeyTenant(dataId, group, tenant)); + log.info(agent.getName(), "[polling-resp] config changed. dataId={}, group={}, tenant={}", dataId, + group, tenant); + } else { + log.error(agent.getName(), "NACOS-XXXX", "[polling-resp] invalid dataIdAndGroup error", + dataIdAndGroup); + } + } + } + return updateList; + } + + @SuppressWarnings("PMD.ThreadPoolCreationRule") + public ClientWorker(final ServerHttpAgent agent, final ConfigFilterChainManager configFilterChainManager) { + this.agent = agent; + this.configFilterChainManager = configFilterChainManager; + executor = Executors.newScheduledThreadPool(1, new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setName("com.alibaba.nacos.client.Worker." + agent.getName()); + t.setDaemon(true); + return t; + } + }); + + executorService = Executors.newCachedThreadPool(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setName("com.alibaba.nacos.client.Worker.longPulling" + agent.getName()); + t.setDaemon(true); + return t; + } + }); + + executor.scheduleWithFixedDelay(new Runnable() { + public void run() { + try { + checkConfigInfo(); + } catch (Throwable e) { + log.error(agent.getName(), "NACOS-XXXX", "[sub-check] rotate check error", e); + } + } + }, 1L, 10L, TimeUnit.MILLISECONDS); + } + + class LongPullingRunnable implements Runnable { + private int taskId; + + public LongPullingRunnable(int taskId) { + this.taskId = taskId; + } + + public void run() { + try { + List cacheDatas = new ArrayList(); + // check failover config + for (CacheData cacheData : cacheMap.get().values()) { + if (cacheData.getTaskId() == taskId) { + cacheDatas.add(cacheData); + try { + checkLocalConfig(cacheData); + if (cacheData.isUseLocalConfigInfo()) { + cacheData.checkListenerMd5(); + } + } catch (Exception e) { + log.error("NACOS-CLIENT", "get local config info error", e); + } + } + } + + List inInitializingCacheList = new ArrayList(); + // check server config + List changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList); + + for (String groupKey : changedGroupKeys) { + String[] key = GroupKey.parseKey(groupKey); + String dataId = key[0]; + String group = key[1]; + String tenant = null; + if (key.length == 3) { + tenant = key[2]; + } + try { + String content = getServerConfig(dataId, group, tenant, 3000L); + CacheData cache = cacheMap.get().get(GroupKey.getKeyTenant(dataId, group, tenant)); + cache.setContent(content); + log.info(agent.getName(), "[data-received] dataId={}, group={}, tenant={}, md5={}, content={}", + dataId, group, tenant, cache.getMd5(), ContentUtils.truncateContent(content)); + } catch (NacosException ioe) { + log.error(agent.getName(), "NACOS-XXXX", + "[get-update] get changed config exception. dataId={}, group={}, tenant={}, msg={}", + dataId, group, tenant, ioe.toString()); + } + } + for (CacheData cacheData : cacheDatas) { + if (!cacheData.isInitializing() || inInitializingCacheList + .contains(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant))) { + cacheData.checkListenerMd5(); + cacheData.setInitializing(false); + } + } + inInitializingCacheList.clear(); + } catch (Throwable e) { + log.error("500", "longPulling error", e); + } finally { + executorService.execute(this); + } + } + } + + // ================= + + public boolean isHealthServer() { + return isHealthServer; + } + + private void setHealthServer(boolean isHealthServer) { + this.isHealthServer = isHealthServer; + } + + final ScheduledExecutorService executor; + final ExecutorService executorService; + /** + * groupKey -> cacheData + */ + AtomicReference> cacheMap = new AtomicReference>(new HashMap()); + ServerHttpAgent agent; + ConfigFilterChainManager configFilterChainManager; + private boolean isHealthServer = true; + private double currentLongingTaskCount = 0; + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/impl/EventDispatcher.java b/client/src/main/java/com/alibaba/nacos/client/config/impl/EventDispatcher.java new file mode 100644 index 00000000000..72412baa5fd --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/impl/EventDispatcher.java @@ -0,0 +1,133 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.config.impl; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; + +import com.alibaba.nacos.client.config.utils.LogUtils; +import com.alibaba.nacos.client.logger.Logger; + + +/** + * Event subscription and publishing tools. + * + * @author Nacos + * + */ +public class EventDispatcher { + + final static public Logger log = LogUtils.logger(EventDispatcher.class); + + /** + * 添加事件监听器 + */ + static public void addEventListener(AbstractEventListener listener) { + for (Class type : listener.interest()) { + getListenerList(type).addIfAbsent(listener); + } + } + + /** + * 发布事件,首先发布该事件暗示的其他事件,最后通知所有对应的监听器。 + */ + static public void fireEvent(AbstractEvent abstractEvent) { + if (null == abstractEvent) { + return; + } + + // 发布该事件暗示的其他事件 + for (AbstractEvent implyEvent : abstractEvent.implyEvents()) { + try { + // 避免死循环 + if (abstractEvent != implyEvent) { + fireEvent(implyEvent); + } + } catch (Exception e) { + log.warn("", e.toString(), e); + } + } + + for (AbstractEventListener listener : getListenerList(abstractEvent.getClass())) { + try { + listener.onEvent(abstractEvent); + } catch (Exception e) { + log.warn(e.toString(), e); + } + } + } + + static synchronized CopyOnWriteArrayList getListenerList( + Class eventType) { + CopyOnWriteArrayList listeners = LISTENER_MAP.get(eventType); + if (null == listeners) { + listeners = new CopyOnWriteArrayList(); + LISTENER_MAP.put(eventType, listeners); + } + return listeners; + } + + // ======================== + + static final Map, CopyOnWriteArrayList> LISTENER_MAP = new HashMap, CopyOnWriteArrayList>(); + + // ======================== + + /** + * Client事件。 + */ + static public abstract class AbstractEvent { + + @SuppressWarnings("unchecked") + protected List implyEvents() { + return Collections.EMPTY_LIST; + } + } + + /** + * 事件监听器。 + */ + static public abstract class AbstractEventListener { + public AbstractEventListener() { + /** + * 自动注册给EventDispatcher + */ + EventDispatcher.addEventListener(this); + } + + /** + * 感兴趣的事件列表 + * + * @return event list + */ + abstract public List> interest(); + + /** + * 处理事件 + * @param abstractEvent event to do + */ + abstract public void onEvent(AbstractEvent abstractEvent); + } + + /** + * serverList has changed + */ + static public class ServerlistChangeEvent extends AbstractEvent { + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/impl/HttpSimpleClient.java b/client/src/main/java/com/alibaba/nacos/client/config/impl/HttpSimpleClient.java new file mode 100644 index 00000000000..3bdcd83e462 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/impl/HttpSimpleClient.java @@ -0,0 +1,276 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.config.impl; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.client.config.common.Constants; +import com.alibaba.nacos.client.config.utils.IOUtils; +import com.alibaba.nacos.client.config.utils.MD5; +import com.alibaba.nacos.client.utils.EnvUtil; +import com.alibaba.nacos.client.utils.ParamUtil; +import com.alibaba.nacos.common.util.UuidUtil; + +/** + * Http tool + * + * @author Nacos + * + */ +public class HttpSimpleClient { + + + static public HttpResult httpGet(String url, List headers, List paramValues, + String encoding, long readTimeoutMs, boolean isSSL) throws IOException{ + String encodedContent = encodingParams(paramValues, encoding); + url += (null == encodedContent) ? "" : ("?" + encodedContent); + if (Limiter.isLimit(MD5.getInstance().getMD5String( + new StringBuilder(url).append(encodedContent).toString()))) { + return new HttpResult(NacosException.CLIENT_OVER_THRESHOLD, + "More than client-side current limit threshold"); + } + + HttpURLConnection conn = null; + + try { + conn = (HttpURLConnection) new URL(url).openConnection(); + conn.setRequestMethod("GET"); + conn.setConnectTimeout(ParamUtil.getConnectTimeout() > 100 ? ParamUtil.getConnectTimeout() : 100); + conn.setReadTimeout((int) readTimeoutMs); + List newHeaders = getHeaders(url, headers, paramValues); + setHeaders(conn, newHeaders, encoding); + + conn.connect(); + + int respCode = conn.getResponseCode(); + String resp = null; + + if (HttpURLConnection.HTTP_OK == respCode) { + resp = IOUtils.toString(conn.getInputStream(), encoding); + } else { + resp = IOUtils.toString(conn.getErrorStream(), encoding); + } + return new HttpResult(respCode, conn.getHeaderFields(), resp); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + } + + + /** + * 发送GET请求。 + */ + static public HttpResult httpGet(String url, List headers, List paramValues, String encoding, + long readTimeoutMs) throws IOException { + return httpGet(url, headers, paramValues, encoding, readTimeoutMs, false); + } + + /** + * 发送POST请求。 + * + * @param url + * @param headers + * 请求Header,可以为null + * @param paramValues + * 参数,可以为null + * @param encoding + * URL编码使用的字符集 + * @param readTimeoutMs + * 响应超时 + * @param isSSL + * 是否https + * @return + * @throws IOException + */ + static public HttpResult httpPost(String url, List headers, List paramValues, + String encoding, long readTimeoutMs, boolean isSSL) throws IOException { + String encodedContent = encodingParams(paramValues, encoding); + if (Limiter.isLimit(MD5.getInstance().getMD5String( + new StringBuilder(url).append(encodedContent).toString()))) { + return new HttpResult(NacosException.CLIENT_OVER_THRESHOLD, + "More than client-side current limit threshold"); + } + HttpURLConnection conn = null; + try { + conn = (HttpURLConnection) new URL(url).openConnection(); + conn.setRequestMethod("POST"); + conn.setConnectTimeout(ParamUtil.getConnectTimeout() > 3000 ? ParamUtil.getConnectTimeout() : 3000); + conn.setReadTimeout((int) readTimeoutMs); + conn.setDoOutput(true); + conn.setDoInput(true); + List newHeaders = getHeaders(url, headers, paramValues); + setHeaders(conn, newHeaders, encoding); + + conn.getOutputStream().write(encodedContent.getBytes(encoding)); + + int respCode = conn.getResponseCode(); + String resp = null; + + if (HttpURLConnection.HTTP_OK == respCode) { + resp = IOUtils.toString(conn.getInputStream(), encoding); + } else { + resp = IOUtils.toString(conn.getErrorStream(), encoding); + } + return new HttpResult(respCode, conn.getHeaderFields(), resp); + } finally { + if (null != conn) { + conn.disconnect(); + } + } + } + + /** + * 发送POST请求。 + * + * @param url + * @param headers + * 请求Header,可以为null + * @param paramValues + * 参数,可以为null + * @param encoding + * URL编码使用的字符集 + * @param readTimeoutMs + * 响应超时 + * @return + * @throws IOException + */ + static public HttpResult httpPost(String url, List headers, List paramValues, String encoding, + long readTimeoutMs) throws IOException { + return httpPost(url, headers, paramValues, encoding, readTimeoutMs, false); + } + + + static public HttpResult httpDelete(String url, List headers, List paramValues, + String encoding, long readTimeoutMs, boolean isSSL) throws IOException{ + String encodedContent = encodingParams(paramValues, encoding); + url += (null == encodedContent) ? "" : ("?" + encodedContent); + if (Limiter.isLimit(MD5.getInstance().getMD5String( + new StringBuilder(url).append(encodedContent).toString()))) { + return new HttpResult(NacosException.CLIENT_OVER_THRESHOLD, + "More than client-side current limit threshold"); + } + + HttpURLConnection conn = null; + + try { + conn = (HttpURLConnection) new URL(url).openConnection(); + conn.setRequestMethod("DELETE"); + conn.setConnectTimeout(ParamUtil.getConnectTimeout() > 100 ? ParamUtil.getConnectTimeout() : 100); + conn.setReadTimeout((int) readTimeoutMs); + List newHeaders = getHeaders(url, headers, paramValues); + setHeaders(conn, newHeaders, encoding); + + conn.connect(); + + int respCode = conn.getResponseCode(); + String resp = null; + + if (HttpURLConnection.HTTP_OK == respCode) { + resp = IOUtils.toString(conn.getInputStream(), encoding); + } else { + resp = IOUtils.toString(conn.getErrorStream(), encoding); + } + return new HttpResult(respCode, conn.getHeaderFields(), resp); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + } + + + static public HttpResult httpDelete(String url, List headers, List paramValues, String encoding, + long readTimeoutMs) throws IOException { + return httpGet(url, headers, paramValues, encoding, readTimeoutMs, false); + } + + static private void setHeaders(HttpURLConnection conn, List headers, String encoding) { + if (null != headers) { + for (Iterator iter = headers.iterator(); iter.hasNext();) { + conn.addRequestProperty(iter.next(), iter.next()); + } + } + conn.addRequestProperty("Client-Version", ParamUtil.getClientVersion()); + conn.addRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=" + encoding); + + String ts = String.valueOf(System.currentTimeMillis()); + String token = MD5.getInstance().getMD5String(ts + ParamUtil.getAppKey()); + + conn.addRequestProperty(Constants.CLIENT_APPNAME_HEADER, ParamUtil.getAppName()); + conn.addRequestProperty(Constants.CLIENT_REQUEST_TS_HEADER, ts); + conn.addRequestProperty(Constants.CLIENT_REQUEST_TOKEN_HEADER, token); + } + + private static List getHeaders(String url, List headers, List paramValues) + throws IOException { + List newHeaders = new ArrayList(); + newHeaders.add("exConfigInfo"); + newHeaders.add("true"); + newHeaders.add("RequestId"); + newHeaders.add(UuidUtil.generateUuid()); + if (headers!=null) { + newHeaders.addAll(headers); + } + return newHeaders; + } + + static private String encodingParams(List paramValues, String encoding) + throws UnsupportedEncodingException { + StringBuilder sb = new StringBuilder(); + if (null == paramValues) { + return null; + } + + for (Iterator iter = paramValues.iterator(); iter.hasNext();) { + sb.append(iter.next()).append("="); + sb.append(URLEncoder.encode(iter.next(), encoding)); + if (iter.hasNext()) { + sb.append("&"); + } + } + return sb.toString(); + } + + static public class HttpResult { + final public int code; + final public Map> headers; + final public String content; + + public HttpResult(int code, String content) { + this.code = code; + this.headers = null; + this.content = content; + } + + public HttpResult(int code, Map> headers, String content) { + this.code = code; + this.headers = headers; + this.content = content; + } + } + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/impl/Limiter.java b/client/src/main/java/com/alibaba/nacos/client/config/impl/Limiter.java new file mode 100644 index 00000000000..7dbec0f10a4 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/impl/Limiter.java @@ -0,0 +1,79 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.config.impl; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import com.alibaba.nacos.client.config.utils.LogUtils; +import com.alibaba.nacos.client.logger.Logger; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.util.concurrent.RateLimiter; + +/** + * Limiter + * + * @author Nacos + * + */ +public class Limiter { + + static final public Logger log = LogUtils.logger(Limiter.class); + private static int CAPACITY_SIZE = 1000; + private static int LIMIT_TIME = 1000; + private static Cache cache = CacheBuilder.newBuilder() + .initialCapacity(CAPACITY_SIZE).expireAfterAccess(1, TimeUnit.MINUTES) + .build(); + + /** + * qps 5 + */ + private static final String DEFAULT_LIMIT = "5"; + private static double limit = 5; + + static { + try { + String limitTimeStr = System + .getProperty("limitTime", DEFAULT_LIMIT); + limit = Double.parseDouble(limitTimeStr); + log.info("limitTime:{}", limit); + } catch (Exception e) { + log.error("Nacos-xxx", "init limitTime fail", e); + } + } + + public static boolean isLimit(String accessKeyID) { + RateLimiter rateLimiter = null; + try { + rateLimiter = cache.get(accessKeyID, new Callable() { + @Override + public RateLimiter call() throws Exception { + return RateLimiter.create(limit); + } + }); + } catch (ExecutionException e) { + log.error("Nacos-XXX", "create limit fail", e); + } + if (rateLimiter != null && !rateLimiter.tryAcquire(LIMIT_TIME, TimeUnit.MILLISECONDS)) { + log.error("Nacos-XXX", "access_key_id:{} limited", accessKeyID); + return true; + } + return false; + } + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/impl/LocalConfigInfoProcessor.java b/client/src/main/java/com/alibaba/nacos/client/config/impl/LocalConfigInfoProcessor.java new file mode 100644 index 00000000000..320f67df8a4 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/impl/LocalConfigInfoProcessor.java @@ -0,0 +1,197 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.config.impl; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import com.alibaba.nacos.client.config.common.Constants; +import com.alibaba.nacos.client.config.utils.ConcurrentDiskUtil; +import com.alibaba.nacos.client.config.utils.IOUtils; +import com.alibaba.nacos.client.config.utils.JVMUtil; +import com.alibaba.nacos.client.config.utils.LogUtils; +import com.alibaba.nacos.client.config.utils.SnapShotSwitch; +import com.alibaba.nacos.client.logger.Logger; +import com.alibaba.nacos.client.utils.StringUtils; + + +/** + * Local Disaster Recovery Directory Tool + * + * @author Nacos + */ +public class LocalConfigInfoProcessor { + + final static public Logger log = LogUtils.logger(LocalConfigInfoProcessor.class); + + static public String getFailover(String serverName, String dataId, String group, String tenant) { + File localPath = getFailoverFile(serverName, dataId, group, tenant); + if (!localPath.exists() || !localPath.isFile()) { + return null; + } + + try { + return readFile(localPath); + } catch (IOException ioe) { + log.error(serverName, "NACOS-XXXX","get failover error, " + localPath + ioe.toString()); + return null; + } + } + + /** + * 获取本地缓存文件内容。NULL表示没有本地文件或抛出异常。 + */ + static public String getSnapshot(String name, String dataId, String group, String tenant) { + if (!SnapShotSwitch.getIsSnapShot()) { + return null; + } + File file = getSnapshotFile(name, dataId, group, tenant); + if (!file.exists() || !file.isFile()) { + return null; + } + + try { + return readFile(file); + } catch (IOException ioe) { + log.error(name, "NACOS-XXXX","get snapshot error, " + file + ", " + ioe.toString()); + return null; + } + } + + static private String readFile(File file) throws IOException { + if (!file.exists() || !file.isFile()) { + return null; + } + + if (JVMUtil.isMultiInstance()) { + return ConcurrentDiskUtil.getFileContent(file, Constants.ENCODE); + } else { + InputStream is = null; + try { + is = new FileInputStream(file); + return IOUtils.toString(is, Constants.ENCODE); + } finally { + try { + if (null != is) { + is.close(); + } + } catch (IOException ioe) { + } + } + } + } + + + static public void saveSnapshot(String envName, String dataId, String group, String tenant, String config) { + if (!SnapShotSwitch.getIsSnapShot()) { + return; + } + File file = getSnapshotFile(envName, dataId, group, tenant); + if (null == config) { + try { + IOUtils.delete(file); + } catch (IOException ioe) { + log.error(envName, "NACOS-XXXX","delete snapshot error, " + file + ", " + ioe.toString()); + } + } else { + try { + boolean isMdOk = file.getParentFile().mkdirs(); + if (!isMdOk) { + log.error(envName, "NACOS-XXXX", "save snapshot error"); + } + if (JVMUtil.isMultiInstance()) { + ConcurrentDiskUtil.writeFileContent(file, config, + Constants.ENCODE); + } else { + IOUtils.writeStringToFile(file, config, Constants.ENCODE); + } + } catch (IOException ioe) { + log.error(envName, "NACOS-XXXX","save snapshot error, " + file + ", " + ioe.toString()); + } + } + } + + /** + * 清除snapshot目录下所有缓存文件。 + */ + static public void cleanAllSnapshot() { + try { + File rootFile = new File(LOCAL_SNAPSHOT_PATH); + File[] files = rootFile.listFiles(); + if (files == null || files.length == 0) { + return; + } + for(File file : files){ + if(file.getName().endsWith("_nacos")){ + IOUtils.cleanDirectory(file); + } + } + } catch (IOException ioe) { + log.error("NACOS-XXXX","clean all snapshot error, " + ioe.toString(), ioe); + } + } + + static public void cleanEnvSnapshot(String envName){ + File tmp = new File(LOCAL_SNAPSHOT_PATH, envName + "_nacos"); + tmp = new File(tmp, "snapshot"); + try { + IOUtils.cleanDirectory(tmp); + log.info("success dlelet " + envName + "-snapshot"); + } catch (IOException e) { + log.info("fail dlelet " + envName + "-snapshot, " + e.toString()); + e.printStackTrace(); + } + } + + + static File getFailoverFile(String serverName, String dataId, String group, String tenant) { + File tmp = new File(LOCAL_SNAPSHOT_PATH, serverName + "_nacos"); + tmp = new File(tmp, "data"); + if (StringUtils.isBlank(tenant)) { + tmp = new File(tmp, "config-data"); + } else + { + tmp = new File(tmp, "config-data-tenant"); + tmp = new File(tmp, tenant); + } + return new File(new File(tmp, group), dataId); + } + + static File getSnapshotFile(String envName, String dataId, String group, String tenant) { + File tmp = new File(LOCAL_SNAPSHOT_PATH, envName + "_nacos"); + if (StringUtils.isBlank(tenant)) { + tmp = new File(tmp, "snapshot"); + } else { + tmp = new File(tmp, "snapshot-tenant"); + tmp = new File(tmp, tenant); + } + + return new File(new File(tmp, group), dataId); + } + + public static final String LOCAL_FILEROOT_PATH; + public static final String LOCAL_SNAPSHOT_PATH; + static { + LOCAL_FILEROOT_PATH = System.getProperty("JM.LOG.PATH", System.getProperty("user.home")) + File.separator + + "nacos" + File.separator + "config"; + LOCAL_SNAPSHOT_PATH = System.getProperty("JM.SNAPSHOT.PATH", System.getProperty("user.home")) + File.separator + + "nacos" + File.separator + "config"; + log.warn("LOCAL_SNAPSHOT_PATH:{}", LOCAL_SNAPSHOT_PATH); + } + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/impl/ServerHttpAgent.java b/client/src/main/java/com/alibaba/nacos/client/config/impl/ServerHttpAgent.java new file mode 100644 index 00000000000..a753f760f49 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/impl/ServerHttpAgent.java @@ -0,0 +1,392 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.config.impl; + +import java.io.IOException; +import java.net.ConnectException; +import java.net.HttpURLConnection; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Properties; + +import org.codehaus.jackson.annotate.JsonProperty; +import org.codehaus.jackson.type.TypeReference; + +import com.alibaba.nacos.api.PropertyKeyConst; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.client.config.common.Constants; +import com.alibaba.nacos.client.config.impl.HttpSimpleClient.HttpResult; +import com.alibaba.nacos.client.config.utils.IOUtils; +import com.alibaba.nacos.client.config.utils.LogUtils; +import com.alibaba.nacos.client.identify.STSConfig; +import com.alibaba.nacos.client.logger.Logger; +import com.alibaba.nacos.client.logger.support.LoggerHelper; +import com.alibaba.nacos.client.utils.JSONUtils; +import com.alibaba.nacos.client.utils.ParamUtil; +import com.alibaba.nacos.client.utils.StringUtils; + +/** + * Server Agent + * + * @author water.lyl + * + */ +public class ServerHttpAgent { + + final static public Logger log = LogUtils.logger(ServerHttpAgent.class); + /** + * @param path + * 相对于web应用根,以/开头 + * @param headers + * @param paramValues + * @param encoding + * @param readTimeoutMs + * @return + * @throws IOException + */ + public HttpResult httpGet(String path, List headers, List paramValues, String encoding, + long readTimeoutMs) throws IOException { + final long endTime = System.currentTimeMillis() + readTimeoutMs; + + boolean isSSL = false; + + do { + try { + List newHeaders = getSpasHeaders(paramValues); + if (headers != null) { + newHeaders.addAll(headers); + } + HttpResult result = HttpSimpleClient.httpGet( + getUrl(serverListMgr.getCurrentServerAddr(), path, isSSL), newHeaders, paramValues, encoding, + readTimeoutMs, isSSL); + if (result.code == HttpURLConnection.HTTP_INTERNAL_ERROR + || result.code == HttpURLConnection.HTTP_BAD_GATEWAY + || result.code == HttpURLConnection.HTTP_UNAVAILABLE) { + log.error("NACOS ConnectException", "currentServerAddr:{}. httpCode:", + new Object[] { serverListMgr.getCurrentServerAddr(), result.code }); + } else { + return result; + } + } catch (ConnectException ce) { + log.error("NACOS ConnectException", "currentServerAddr:{}", + new Object[] { serverListMgr.getCurrentServerAddr() }); + serverListMgr.refreshCurrentServerAddr(); + } catch (SocketTimeoutException stoe) { + log.error("NACOS SocketTimeoutException", "currentServerAddr:{}", + new Object[] { serverListMgr.getCurrentServerAddr()}); + serverListMgr.refreshCurrentServerAddr(); + } catch (IOException ioe) { + log.error("NACOS IOException", "currentServerAddr:{}", + new Object[] { serverListMgr.getCurrentServerAddr()}); + throw ioe; + } + } while (System.currentTimeMillis() > endTime); + + log.error("NACOS-0002", + LoggerHelper.getErrorCodeStr("NACOS", "NACOS-0002", "环境问题", "no available server")); + throw new ConnectException("no available server"); + } + + public HttpResult httpPost(String path, List headers, List paramValues, String encoding, + long readTimeoutMs) throws IOException { + final long endTime = System.currentTimeMillis() + readTimeoutMs; + boolean isSSL = false; + do { + try { + List newHeaders = getSpasHeaders(paramValues); + if (headers != null) { + newHeaders.addAll(headers); + } + HttpResult result = HttpSimpleClient.httpPost( + getUrl(serverListMgr.getCurrentServerAddr(), path, isSSL), newHeaders, paramValues, encoding, + readTimeoutMs, isSSL); + if (result.code == HttpURLConnection.HTTP_INTERNAL_ERROR + || result.code == HttpURLConnection.HTTP_BAD_GATEWAY + || result.code == HttpURLConnection.HTTP_UNAVAILABLE) { + log.error("NACOS ConnectException", "currentServerAddr:{}. httpCode:", + new Object[] { serverListMgr.getCurrentServerAddr(), result.code }); + } else { + return result; + } + } catch (ConnectException ce) { + log.error("NACOS ConnectException", "currentServerAddr:{}", + new Object[] { serverListMgr.getCurrentServerAddr()}); + serverListMgr.refreshCurrentServerAddr(); + } catch (SocketTimeoutException stoe) { + log.error("NACOS SocketTimeoutException", "currentServerAddr:{}", + new Object[] { serverListMgr.getCurrentServerAddr()}); + serverListMgr.refreshCurrentServerAddr(); + } catch (IOException ioe) { + log.error("NACOS IOException", "currentServerAddr:{}", + new Object[] { serverListMgr.getCurrentServerAddr()}); + throw ioe; + } + + } while (System.currentTimeMillis() > endTime); + + log.error("NACOS-0002", + LoggerHelper.getErrorCodeStr("NACOS", "NACOS-0002", "环境问题", "no available server")); + throw new ConnectException("no available server"); + } + + public HttpResult httpDelete(String path, List headers, List paramValues, String encoding, + long readTimeoutMs) throws IOException { + final long endTime = System.currentTimeMillis() + readTimeoutMs; + boolean isSSL = false; + do { + try { + List newHeaders = getSpasHeaders(paramValues); + if (headers != null) { + newHeaders.addAll(headers); + } + HttpResult result = HttpSimpleClient.httpDelete( + getUrl(serverListMgr.getCurrentServerAddr(), path, isSSL), newHeaders, paramValues, encoding, + readTimeoutMs, isSSL); + if (result.code == HttpURLConnection.HTTP_INTERNAL_ERROR + || result.code == HttpURLConnection.HTTP_BAD_GATEWAY + || result.code == HttpURLConnection.HTTP_UNAVAILABLE) { + log.error("NACOS ConnectException", "currentServerAddr:{}. httpCode:", + new Object[] { serverListMgr.getCurrentServerAddr(), result.code }); + } else { + return result; + } + } catch (ConnectException ce) { + log.error("NACOS ConnectException", "currentServerAddr:{}", + new Object[] { serverListMgr.getCurrentServerAddr()}); + serverListMgr.refreshCurrentServerAddr(); + } catch (SocketTimeoutException stoe) { + log.error("NACOS SocketTimeoutException", "currentServerAddr:{}", + new Object[] { serverListMgr.getCurrentServerAddr()}); + serverListMgr.refreshCurrentServerAddr(); + } catch (IOException ioe) { + log.error("NACOS IOException", "currentServerAddr:{}", + new Object[] { serverListMgr.getCurrentServerAddr()}); + throw ioe; + } + + } while (System.currentTimeMillis() > endTime); + + log.error("NACOS-0002", + LoggerHelper.getErrorCodeStr("NACOS", "NACOS-0002", "环境问题", "no available server")); + throw new ConnectException("no available server"); + } + + private String getUrl(String serverAddr, String relativePath, boolean isSSL) { + String httpPrefix = "http://"; + if (isSSL) { + httpPrefix = "https://"; + } + return httpPrefix + serverAddr + "/" + serverListMgr.getContentPath() + relativePath; + } + + public static String getAppname() { + return ParamUtil.getAppName(); + } + + public ServerHttpAgent(ServerListManager mgr) { + serverListMgr = mgr; + } + + public ServerHttpAgent(ServerListManager mgr, Properties properties) { + serverListMgr = mgr; + String ak = properties.getProperty(PropertyKeyConst.ACCESS_KEY); + if (StringUtils.isBlank(ak)) { + accessKey = SpasAdapter.getAk(); + } else { + accessKey = ak; + } + + String sk = properties.getProperty(PropertyKeyConst.SECRET_KEY); + if (StringUtils.isBlank(sk)) { + secretKey = SpasAdapter.getSk(); + } else { + secretKey = sk; + } + } + + public ServerHttpAgent(Properties properties) throws NacosException { + String encodeTmp = properties.getProperty(PropertyKeyConst.ENCODE); + if (StringUtils.isBlank(encodeTmp)) { + encode = Constants.ENCODE; + } else { + encode = encodeTmp.trim(); + } + serverListMgr = new ServerListManager(properties); + String ak = properties.getProperty(PropertyKeyConst.ACCESS_KEY); + if (StringUtils.isBlank(ak)) { + accessKey = SpasAdapter.getAk(); + } else { + accessKey = ak; + } + + String sk = properties.getProperty(PropertyKeyConst.SECRET_KEY); + if (StringUtils.isBlank(sk)) { + secretKey = SpasAdapter.getSk(); + } else { + secretKey = sk; + } + } + + public synchronized void start() throws NacosException { + serverListMgr.start(); + } + + private List getSpasHeaders(List paramValues) throws IOException { + List newHeaders = new ArrayList(); + // STS 临时凭证鉴权的优先级高于 AK/SK 鉴权 + if (STSConfig.getInstance().isSTSOn()) { + STSCredential sTSCredential = getSTSCredential(); + accessKey = sTSCredential.accessKeyId; + secretKey = sTSCredential.accessKeySecret; + newHeaders.add("Spas-SecurityToken"); + newHeaders.add(sTSCredential.securityToken); + } + + if (StringUtils.isNotEmpty(accessKey) && StringUtils.isNotEmpty(secretKey)) { + newHeaders.add("Spas-AccessKey"); + newHeaders.add(accessKey); + List signHeaders = SpasAdapter.getSignHeaders(paramValues, secretKey); + if (signHeaders != null) { + newHeaders.addAll(signHeaders); + } + } + return newHeaders; + } + + private STSCredential getSTSCredential() throws IOException { + boolean cacheSecurityCredentials = STSConfig.getInstance().isCacheSecurityCredentials(); + if (cacheSecurityCredentials && sTSCredential != null) { + long currentTime = System.currentTimeMillis(); + long expirationTime = sTSCredential.expiration.getTime(); + int timeToRefreshInMillisecond = STSConfig.getInstance().getTimeToRefreshInMillisecond(); + if (expirationTime - currentTime > timeToRefreshInMillisecond) { + return sTSCredential; + } + } + String stsResponse = getSTSResponse(); + STSCredential stsCredentialTmp = (STSCredential)JSONUtils.deserializeObject(stsResponse, + new TypeReference() {}); + sTSCredential = stsCredentialTmp; + log.info("getSTSCredential", "code:{}, accessKeyId:{}, lastUpdated:{}, expiration:{}", sTSCredential.getCode(), + sTSCredential.getAccessKeyId(), sTSCredential.getLastUpdated(), sTSCredential.getExpiration()); + return sTSCredential; + } + + private static String getSTSResponse() throws IOException { + String securityCredentials = STSConfig.getInstance().getSecurityCredentials(); + if (securityCredentials != null) { + return securityCredentials; + } + String securityCredentialsUrl = STSConfig.getInstance().getSecurityCredentialsUrl(); + HttpURLConnection conn = null; + int respCode; + String response; + try { + conn = (HttpURLConnection)new URL(securityCredentialsUrl).openConnection(); + conn.setRequestMethod("GET"); + conn.setConnectTimeout(ParamUtil.getConnectTimeout() > 100 ? ParamUtil.getConnectTimeout() : 100); + conn.setReadTimeout(1000); + conn.connect(); + respCode = conn.getResponseCode(); + if (HttpURLConnection.HTTP_OK == respCode) { + response = IOUtils.toString(conn.getInputStream(), Constants.ENCODE); + } else { + response = IOUtils.toString(conn.getErrorStream(), Constants.ENCODE); + } + } catch (IOException e) { + log.error("500", "can not get security credentials", e); + throw e; + } finally { + if (null != conn) { + conn.disconnect(); + } + } + if (HttpURLConnection.HTTP_OK == respCode) { + return response; + } + log.error(respCode + "", "can not get security credentials, securityCredentialsUrl:{}, response:{}", + new Object[] {securityCredentialsUrl, response}); + throw new IOException("can not get security credentials, responseCode: " + respCode + ", response: " + response); + } + + public String getName() { + return serverListMgr.getName(); + } + + public String getNamespace() { + return serverListMgr.getNamespace(); + } + public String getTenant() { + return serverListMgr.getTenant(); + } + + public String getEncode() { + return encode; + } + + @SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") + private static class STSCredential { + @JsonProperty(value = "AccessKeyId") + private String accessKeyId; + @JsonProperty(value = "AccessKeySecret") + private String accessKeySecret; + @JsonProperty(value = "Expiration") + private Date expiration; + @JsonProperty(value = "SecurityToken") + private String securityToken; + @JsonProperty(value = "LastUpdated") + private Date lastUpdated; + @JsonProperty(value = "Code") + private String code; + + public String getAccessKeyId() { + return accessKeyId; + } + + public Date getExpiration() { + return expiration; + } + + public Date getLastUpdated() { + return lastUpdated; + } + + public String getCode() { + return code; + } + + public String toString() { + return "STSCredential{" + + "accessKeyId='" + accessKeyId + '\'' + + ", accessKeySecret='" + accessKeySecret + '\'' + + ", expiration=" + expiration + + ", securityToken='" + securityToken + '\'' + + ", lastUpdated=" + lastUpdated + + ", code='" + code + '\'' + + '}'; + } + } + + private String accessKey; + private String secretKey; + private String encode; + private volatile STSCredential sTSCredential; + final ServerListManager serverListMgr; + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/impl/ServerListManager.java b/client/src/main/java/com/alibaba/nacos/client/config/impl/ServerListManager.java new file mode 100644 index 00000000000..ad94d5963dd --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/impl/ServerListManager.java @@ -0,0 +1,432 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.config.impl; + +import java.io.IOException; +import java.io.StringReader; +import java.net.HttpURLConnection; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Properties; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import com.alibaba.nacos.api.PropertyKeyConst; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.client.config.impl.EventDispatcher.ServerlistChangeEvent; +import com.alibaba.nacos.client.config.impl.HttpSimpleClient.HttpResult; +import com.alibaba.nacos.client.config.utils.IOUtils; +import com.alibaba.nacos.client.config.utils.LogUtils; +import com.alibaba.nacos.client.logger.Logger; +import com.alibaba.nacos.client.logger.support.LoggerHelper; +import com.alibaba.nacos.client.utils.EnvUtil; +import com.alibaba.nacos.client.utils.ParamUtil; +import com.alibaba.nacos.client.utils.StringUtils; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + + +/** + * Serverlist Manager + * + * @author Nacos + */ +public class ServerListManager { + + final static public Logger log = LogUtils.logger(ServerListManager.class); + public ServerListManager() { + isFixed = false; + isStarted = false; + name = DEFAULT_NAME; + } + + public ServerListManager(List fixed) { + this(fixed, null); + } + + public ServerListManager(List fixed, String namespace) { + isFixed = true; + isStarted = true; + List serverAddrs = new ArrayList(); + for (String serverAddr : fixed) { + String[] serverAddrArr = serverAddr.split(":"); + if (serverAddrArr.length == 1) { + serverAddrs.add(serverAddrArr[0] + ":" + ParamUtil.getDefaultServerPort()); + } else { + serverAddrs.add(serverAddr); + } + } + serverUrls = new ArrayList(serverAddrs); + if (StringUtils.isBlank(namespace)) { + name = FIXED_NAME + "-" + getFixedNameSuffix(serverAddrs.toArray(new String[serverAddrs.size()])); + } else { + this.namespace = namespace; + name = FIXED_NAME + "-" + getFixedNameSuffix(serverAddrs.toArray(new String[serverAddrs.size()])) + "-" + + namespace; + } + } + + public ServerListManager(String host, int port) { + isFixed = false; + isStarted = false; + name = CUSTOM_NAME + "-" + host + "-" + port; + addressServerUrl = String.format("http://%s:%d/%s/%s", host, port, contentPath, serverListName); + } + + public ServerListManager(String endpoint) throws NacosException { + this(endpoint, null); + } + + public ServerListManager(String endpoint, String namespace) throws NacosException { + isFixed = false; + isStarted = false; + if (StringUtils.isBlank(endpoint)) { + throw new NacosException(NacosException.CLIENT_INVALID_PARAM, "endpoint is blank"); + } + if (StringUtils.isBlank(namespace)) { + name = endpoint; + addressServerUrl = String.format("http://%s:%d/%s/%s", endpoint, endpointPort, contentPath, + serverListName); + } else { + if (StringUtils.isBlank(endpoint)) { + throw new NacosException(NacosException.CLIENT_INVALID_PARAM, "endpoint is blank"); + } + name = endpoint + "-" + namespace; + this.namespace = namespace; + this.tenant = namespace; + addressServerUrl = String.format("http://%s:%d/%s/%s?namespace=%s", endpoint, endpointPort, contentPath, + serverListName, namespace); + } + } + + public ServerListManager(Properties properties) throws NacosException { + isStarted = false; + String serverAddrsStr = properties.getProperty(PropertyKeyConst.SERVER_ADDR); + String namespace = properties.getProperty(PropertyKeyConst.NAMESPACE); + initParam(properties); + if (StringUtils.isNotEmpty(serverAddrsStr)) { + isFixed = true; + List serverAddrs = new ArrayList(); + String[] serverAddrsArr = serverAddrsStr.split(","); + for (String serverAddr : serverAddrsArr) { + String[] serverAddrArr = serverAddr.split(":"); + if (serverAddrArr.length == 1) { + serverAddrs.add(serverAddrArr[0] + ":" + ParamUtil.getDefaultServerPort()); + } else { + serverAddrs.add(serverAddr); + } + } + serverUrls = serverAddrs; + if (StringUtils.isBlank(namespace)) { + name = FIXED_NAME + "-" + getFixedNameSuffix(serverUrls.toArray(new String[serverUrls.size()])); + } else { + this.namespace = namespace; + this.tenant = namespace; + name = FIXED_NAME + "-" + getFixedNameSuffix(serverUrls.toArray(new String[serverUrls.size()])) + "-" + + namespace; + } + } else { + if (StringUtils.isBlank(endpoint)) { + throw new NacosException(NacosException.CLIENT_INVALID_PARAM, "endpoint is blank"); + } + isFixed = false; + if (StringUtils.isBlank(namespace)) { + name = endpoint; + addressServerUrl = String.format("http://%s:%d/%s/%s", endpoint, endpointPort, contentPath, + serverListName); + } else { + this.namespace = namespace; + this.tenant = namespace; + name = endpoint + "-" + namespace; + addressServerUrl = String.format("http://%s:%d/%s/%s?namespace=%s", endpoint, endpointPort, + contentPath, serverListName, namespace); + } + } + } + + private void initParam(Properties properties) { + String endpointTmp = properties.getProperty(PropertyKeyConst.ENDPOINT); + if (!StringUtils.isBlank(endpointTmp)) { + endpoint = endpointTmp; + } + String contentPathTmp = properties.getProperty(PropertyKeyConst.CONTEXT_PATH); + if (!StringUtils.isBlank(contentPathTmp)) { + contentPath = contentPathTmp; + } + String serverListNameTmp = properties.getProperty(PropertyKeyConst.CLUSTER_NAME); + if (!StringUtils.isBlank(serverListNameTmp)) { + serverListName = serverListNameTmp; + } + } + + public synchronized void start() throws NacosException { + + if (isStarted || isFixed) { + return; + } + + GetServerListTask getServersTask = new GetServerListTask(addressServerUrl); + for (int i = 0; i < initServerlistRetryTimes && serverUrls.isEmpty(); ++i) { + getServersTask.run(); + try { + this.wait((i + 1) * 100L); + } catch (Exception e) { + log.warn("get serverlist fail,url: " + addressServerUrl); + } + } + + if (serverUrls.isEmpty()) { + log.error("NACOS-0008", LoggerHelper.getErrorCodeStr("NACOS", "NACOS-0008", "环境问题", + "fail to get NACOS-server serverlist! env:" + name + ", not connnect url:" + addressServerUrl)); + log.error(name, "NACOS-XXXX", "[init-serverlist] fail to get NACOS-server serverlist!"); + throw new NacosException(NacosException.SERVER_ERROR, + "fail to get NACOS-server serverlist! env:" + name + ", not connnect url:" + addressServerUrl); + } + + TimerService.scheduleWithFixedDelay(getServersTask, 0L, 30L, TimeUnit.SECONDS); + isStarted = true; + } + + Iterator iterator() { + if (serverUrls.isEmpty()) { + log.error(name, "NACOS-XXXX", "[iterator-serverlist] No server address defined!"); + } + return new ServerAddressIterator(serverUrls); + } + + + class GetServerListTask implements Runnable { + final String url; + + GetServerListTask(String url) { + this.url = url; + } + + @Override + public void run() { + /** + * get serverlist from nameserver + */ + try { + updateIfChanged(getApacheServerList(url, name)); + } catch (Exception e) { + log.error(name, "NACOS-XXXX", "[update-serverlist] failed to update serverlist from address server!", e); + } + } + } + + private void updateIfChanged(List newList) { + if (null == newList || newList.isEmpty()) { + + log.warn("NACOS-0001", LoggerHelper.getErrorCodeStr("NACOS", "NACOS-0001", "环境问题","[update-serverlist] current serverlist from address server is empty!!!")); + log.warn(name, "[update-serverlist] current serverlist from address server is empty!!!"); + return; + } + /** + * no change + */ + if (newList.equals(serverUrls)) { + return; + } + serverUrls = new ArrayList(newList); + currentServerAddr = iterator().next(); + + EventDispatcher.fireEvent(new ServerlistChangeEvent()); + log.info(name, "[update-serverlist] serverlist updated to {}", serverUrls); + } + + private List getApacheServerList(String url, String name) { + try { + HttpResult httpResult = HttpSimpleClient.httpGet(url, null, null, null, 3000); + + if (HttpURLConnection.HTTP_OK == httpResult.code) { + if (DEFAULT_NAME.equals(name) ) { + EnvUtil.setSelfEnv(httpResult.headers); + } + List lines = IOUtils.readLines(new StringReader(httpResult.content)); + List result = new ArrayList(lines.size()); + for (String serverAddr : lines) { + if (null == serverAddr || serverAddr.trim().isEmpty()) { + continue; + } else { + String[] ipPort = serverAddr.trim().split(":"); + String ip = ipPort[0].trim(); + if (ipPort.length == 1) { + result.add(ip + ":" + ParamUtil.getDefaultServerPort()); + } else { + result.add(serverAddr); + } + } + } + return result; + } else { + log.error(addressServerUrl, "NACOS-XXXX", "[check-serverlist] error. code={}", httpResult.code); + return null; + } + } catch (IOException e) { + log.error("NACOS-0001", LoggerHelper.getErrorCodeStr("NACOS", "NACOS-0001", "环境问题",e.toString())); + log.error(addressServerUrl, "NACOS-XXXX", "[check-serverlist] exception. msg={}", e.toString(), e); + return null; + } + } + + String getUrlString() { + return serverUrls.toString(); + } + + String getFixedNameSuffix(String... serverIps) { + StringBuilder sb = new StringBuilder(); + String split = ""; + for (String serverIp : serverIps) { + sb.append(split); + sb.append(serverIp); + split = "-"; + } + return sb.toString(); + } + + @Override + public String toString() { + return "ServerManager-" + name + "-" +getUrlString(); + } + + public boolean contain(String ip){ + + return serverUrls.contains(ip); + } + + public void refreshCurrentServerAddr() { + currentServerAddr = iterator().next(); + } + + public String getCurrentServerAddr() { + if (StringUtils.isBlank(currentServerAddr)) { + currentServerAddr = iterator().next(); + } + return currentServerAddr; + } + + + public String getContentPath() { + return contentPath; + } + + public String getName() { + return name; + } + + public String getNamespace() { + return namespace; + } + + public String getTenant() { + return tenant; + } + + /** + * 不同环境的名称 + */ + private String name; + private String namespace = ""; + private String tenant = ""; + static public final String DEFAULT_NAME = "default"; + static public final String CUSTOM_NAME = "custom"; + static public final String FIXED_NAME = "fixed"; + private int initServerlistRetryTimes = 5; + /** + * 和其他server的连接超时和socket超时 + */ + static final int TIMEOUT = 5000; + + final boolean isFixed; + boolean isStarted = false; + private String endpoint; + private int endpointPort = 8080; + private String contentPath = ParamUtil.getDefaultContextPath(); + private String serverListName = ParamUtil.getDefaultNodesPath(); + volatile List serverUrls = new ArrayList(); + + private volatile String currentServerAddr; + + public String serverPort = ParamUtil.getDefaultServerPort(); + + public String addressServerUrl; + +} + + +/** + * 对地址列表排序,同机房优先。 + */ +class ServerAddressIterator implements Iterator { + + static class RandomizedServerAddress implements Comparable { + static Random random = new Random(); + + String serverIp; + int priority = 0; + int seed; + + public RandomizedServerAddress(String ip) { + try { + this.serverIp = ip; + /** + * change random scope from 32 to Integer.MAX_VALUE to fix load balance issue + */ + this.seed = random.nextInt(Integer.MAX_VALUE); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + @SuppressFBWarnings("EQ_COMPARETO_USE_OBJECT_EQUALS") + public int compareTo(RandomizedServerAddress other) { + if (this.priority != other.priority) { + return other.priority - this.priority; + } else { + return other.seed - this.seed; + } + } + } + + public ServerAddressIterator(List source) { + sorted = new ArrayList(); + for (String address : source) { + sorted.add(new RandomizedServerAddress(address)); + } + Collections.sort(sorted); + iter = sorted.iterator(); + } + + public boolean hasNext() { + return iter.hasNext(); + } + + public String next() { + return iter.next().serverIp; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + final List sorted; + final Iterator iter; +} + diff --git a/client/src/main/java/com/alibaba/nacos/client/config/impl/SpasAdapter.java b/client/src/main/java/com/alibaba/nacos/client/config/impl/SpasAdapter.java new file mode 100644 index 00000000000..a23e1b41a50 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/impl/SpasAdapter.java @@ -0,0 +1,111 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.config.impl; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import com.alibaba.nacos.client.config.common.Constants; +import com.alibaba.nacos.client.identify.Base64; +import com.alibaba.nacos.client.identify.CredentialService; +import com.alibaba.nacos.client.utils.StringUtils; + +/** + * 适配spas接口 + * + * @author Nacos + * + */ +public class SpasAdapter { + + public static List getSignHeaders(String resource, String secretKey) { + List header = new ArrayList(); + String timeStamp = String.valueOf(System.currentTimeMillis()); + header.add("Timestamp"); + header.add(timeStamp); + if (secretKey != null) { + header.add("Spas-Signature"); + String signature = ""; + if (StringUtils.isBlank(resource)) { + signature = signWithhmacSHA1Encrypt(timeStamp, secretKey); + } else { + signature = signWithhmacSHA1Encrypt(resource + "+" + timeStamp, secretKey); + } + header.add(signature); + } + return header; + } + + + public static List getSignHeaders(List paramValues, String secretKey) { + if (null == paramValues) { + return null; + } + Map signMap = new HashMap(5); + for (Iterator iter = paramValues.iterator(); iter.hasNext();) { + String key = iter.next(); + if (TENANT_KEY.equals(key) || GROUP_KEY.equals(key)) { + signMap.put(key, iter.next()); + } else { + iter.next(); + } + } + String resource = ""; + if (signMap.size() > 1) { + resource = signMap.get(TENANT_KEY) + "+" + signMap.get(GROUP_KEY); + } else { + if (!StringUtils.isBlank(signMap.get(GROUP_KEY))) { + resource = signMap.get(GROUP_KEY); + } + } + return getSignHeaders(resource, secretKey); + } + + public static String getSk() { + return CredentialService.getInstance().getCredential().getSecretKey(); + } + + public static String getAk() { + return CredentialService.getInstance().getCredential().getAccessKey(); + } + + public static String signWithhmacSHA1Encrypt(String encryptText, String encryptKey) { + try { + byte[] data = encryptKey.getBytes("UTF-8"); + // 根据给定的字节数组构造一个密钥,第二参数指定一个密钥算法的名称 + SecretKey secretKey = new SecretKeySpec(data, "HmacSHA1"); + // 生成一个指定 Mac 算法 的 Mac 对象 + Mac mac = Mac.getInstance("HmacSHA1"); + // 用给定密钥初始化 Mac 对象 + mac.init(secretKey); + byte[] text = encryptText.getBytes("UTF-8"); + byte[] textFinal = mac.doFinal(text); + // 完成 Mac 操作, base64编码,将byte数组转换为字符串 + return new String(Base64.encodeBase64(textFinal), Constants.ENCODE); + } catch (Exception e) { + throw new RuntimeException("signWithhmacSHA1Encrypt fail", e); + } + } + + private static String GROUP_KEY = "group"; + private static String TENANT_KEY = "tenant"; +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/impl/TimerService.java b/client/src/main/java/com/alibaba/nacos/client/config/impl/TimerService.java new file mode 100644 index 00000000000..3140220ee79 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/impl/TimerService.java @@ -0,0 +1,48 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.config.impl; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + + +/** + * Time Service + * @author Nacos + * + */ +public class TimerService { + + static public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, + long delay, TimeUnit unit) { + return scheduledExecutor.scheduleWithFixedDelay(command, initialDelay, delay, unit); + } + + @SuppressWarnings("PMD.ThreadPoolCreationRule") + static ScheduledExecutorService scheduledExecutor = Executors + .newSingleThreadScheduledExecutor(new ThreadFactory() { + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setName("com.alibaba.nacos.client.Timer"); + t.setDaemon(true); + return t; + } + }); + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/listener/impl/PropertiesListener.java b/client/src/main/java/com/alibaba/nacos/client/config/listener/impl/PropertiesListener.java new file mode 100644 index 00000000000..72451803036 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/listener/impl/PropertiesListener.java @@ -0,0 +1,61 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.config.listener.impl; + +import com.alibaba.nacos.api.config.listener.AbstractListener; +import com.alibaba.nacos.client.config.utils.LogUtils; +import com.alibaba.nacos.client.logger.Logger; +import com.alibaba.nacos.client.utils.StringUtils; + +import java.io.IOException; +import java.io.StringReader; +import java.util.Properties; + +/** + * Properties Listener + * + * @author Nacos + * + */ +@SuppressWarnings("PMD.AbstractClassShouldStartWithAbstractNamingRule") +public abstract class PropertiesListener extends AbstractListener { + final static public Logger log = LogUtils.logger(PropertiesListener.class); + + public void receiveConfigInfo(String configInfo) { + if (StringUtils.isEmpty(configInfo)) { + return; + } + + Properties properties = new Properties(); + try { + properties.load(new StringReader(configInfo)); + innerReceive(properties); + } + catch (IOException e) { + log.error("NACOS-XXXX","load properties error:" + configInfo, e); + } + + } + + /** + * properties type for receiver + * + * @param properties + * properties + */ + public abstract void innerReceive(Properties properties); + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/utils/ConcurrentDiskUtil.java b/client/src/main/java/com/alibaba/nacos/client/config/utils/ConcurrentDiskUtil.java new file mode 100644 index 00000000000..194a0efc6e4 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/utils/ConcurrentDiskUtil.java @@ -0,0 +1,247 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.config.utils; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; + +import com.alibaba.nacos.client.config.common.Constants; +import com.alibaba.nacos.client.logger.Logger; + +/** + * concurrent disk util;op file with file lock + * + * @author configCenter + * + */ +public class ConcurrentDiskUtil { + + /** + * get file content + * + * @param path + * file path + * @param charsetName + * charsetName + * @return content + * @throws IOException + * IOException + */ + public static String getFileContent(String path, String charsetName) + throws IOException { + File file = new File(path); + return getFileContent(file, charsetName); + } + + /** + * get file content + * + * @param file + * file + * @param charsetName + * charsetName + * @return content + * @throws IOException + * IOException + */ + public static String getFileContent(File file, String charsetName) + throws IOException { + RandomAccessFile fis = null; + FileLock rlock = null; + try { + fis = new RandomAccessFile(file, "r"); + FileChannel fcin = fis.getChannel(); + int i = 0; + do { + try { + rlock = fcin.tryLock(0L, Long.MAX_VALUE, true); + } catch (Exception e) { + ++i; + if (i > RETRY_COUNT) { + log.error("read {} fail;retryed time:{}", + file.getName(), i); + throw new IOException("read " + file.getAbsolutePath() + + " conflict"); + } + sleep(SLEEP_BASETIME * i); + log.warn("read {} conflict;retry time:{}", file.getName(), + i); + } + } while (null == rlock); + int fileSize = (int) fcin.size(); + ByteBuffer byteBuffer = ByteBuffer.allocate(fileSize); + fcin.read(byteBuffer); + byteBuffer.flip(); + return byteBufferToString(byteBuffer, charsetName); + } finally { + if (rlock != null) { + rlock.release(); + rlock = null; + } + if (fis != null) { + fis.close(); + fis = null; + } + } + } + + /** + * write file content + * + * @param path + * file path + * @param content + * content + * @param charsetName + * charsetName + * @return whether write ok + * @throws IOException + * IOException + */ + public static Boolean writeFileContent(String path, String content, + String charsetName) throws IOException { + File file = new File(path); + return writeFileContent(file, content, charsetName); + } + + /** + * write file content + * + * @param file + * file + * @param content + * content + * @param charsetName + * charsetName + * @return whether write ok + * @throws IOException + * IOException + */ + public static Boolean writeFileContent(File file, String content, + String charsetName) throws IOException { + if (!file.exists()) { + boolean isCreateOk = file.createNewFile(); + if (!isCreateOk) { + return false; + } + } + FileChannel channel = null; + FileLock lock = null; + RandomAccessFile raf = null; + try { + raf = new RandomAccessFile(file, "rw"); + channel = raf.getChannel(); + int i = 0; + do { + try { + lock = channel.tryLock(); + } catch (Exception e) { + ++i; + if (i > RETRY_COUNT) { + log.error("write {} fail;retryed time:{}", + file.getName(), i); + throw new IOException("write " + file.getAbsolutePath() + + " conflict"); + } + sleep(SLEEP_BASETIME * i); + log.warn("write {} conflict;retry time:{}", file.getName(), + i); + } + } while (null == lock); + + ByteBuffer sendBuffer = ByteBuffer.wrap(content + .getBytes(charsetName)); + while (sendBuffer.hasRemaining()) { + channel.write(sendBuffer); + } + channel.truncate(content.length()); + } catch (FileNotFoundException e) { + throw new IOException("file not exist"); + } finally { + if (lock != null) { + try { + lock.release(); + lock = null; + } catch (IOException e) { + log.warn("close wrong", e); + } + } + if (channel != null) { + try { + channel.close(); + channel = null; + } catch (IOException e) { + log.warn("close wrong", e); + } + } + if (raf != null) { + try { + raf.close(); + raf = null; + } catch (IOException e) { + log.warn("close wrong", e); + } + } + + } + return true; + } + + /** + * transfer ByteBuffer to String + * + * @param buffer + * buffer + * @param charsetName + * charsetName + * @return String + * @throws IOException + * IOException + */ + public static String byteBufferToString(ByteBuffer buffer, + String charsetName) throws IOException { + Charset charset = null; + CharsetDecoder decoder = null; + CharBuffer charBuffer = null; + charset = Charset.forName(charsetName); + decoder = charset.newDecoder(); + charBuffer = decoder.decode(buffer.asReadOnlyBuffer()); + return charBuffer.toString(); + } + + private static void sleep(int time) { + try { + Thread.sleep(time); + } catch (InterruptedException e) { + log.warn("sleep wrong", e); + } + } + + static final public Logger log = LogUtils.logger(ConcurrentDiskUtil.class); + static final int RETRY_COUNT = 10; + /** + * ms + */ + static final int SLEEP_BASETIME = 10; +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/utils/ContentUtils.java b/client/src/main/java/com/alibaba/nacos/client/config/utils/ContentUtils.java new file mode 100644 index 00000000000..1be12caba30 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/utils/ContentUtils.java @@ -0,0 +1,78 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.config.utils; + +import static com.alibaba.nacos.client.config.common.Constants.WORD_SEPARATOR; + +import com.alibaba.nacos.client.config.common.Constants; + +/** + * Content Util + * + * @author Nacos + * + */ +public class ContentUtils { + + public static void verifyIncrementPubContent(String content) { + + if (content == null || content.length() == 0) { + throw new IllegalArgumentException("发布/删除内容不能为空"); + } + for (int i = 0; i < content.length(); i++) { + char c = content.charAt(i); + if (c == '\r' || c == '\n') { + throw new IllegalArgumentException("发布/删除内容不能包含回车和换行"); + } + if (c == Constants.WORD_SEPARATOR.charAt(0)) { + throw new IllegalArgumentException("发布/删除内容不能包含(char)2"); + } + } + } + + + public static String getContentIdentity(String content) { + int index = content.indexOf(WORD_SEPARATOR); + if (index == -1) { + throw new IllegalArgumentException("内容没有包含分隔符"); + } + return content.substring(0, index); + } + + + public static String getContent(String content) { + int index = content.indexOf(WORD_SEPARATOR); + if (index == -1) { + throw new IllegalArgumentException("内容没有包含分隔符"); + } + return content.substring(index + 1); + } + + + public static String truncateContent(String content) { + if (content == null) { + return ""; + } + else if (content.length() <= SHOW_CONTENT_SIZE) { + return content; + } + else { + return content.substring(0, SHOW_CONTENT_SIZE) + "..."; + } + } + + private static int SHOW_CONTENT_SIZE = 100; +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/utils/IOUtils.java b/client/src/main/java/com/alibaba/nacos/client/config/utils/IOUtils.java new file mode 100644 index 00000000000..5b3ee3a0996 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/utils/IOUtils.java @@ -0,0 +1,151 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.config.utils; + +import java.io.BufferedReader; +import java.io.CharArrayWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.nacos.client.config.common.Constants; + + +/** + * IO Util + * + * @author Nacos + * + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public class IOUtils { + + static public String toString(InputStream input, String encoding) throws IOException { + return (null == encoding) ? toString(new InputStreamReader(input, Constants.ENCODE)) + : toString(new InputStreamReader(input, encoding)); + } + + static public String toString(Reader reader) throws IOException { + CharArrayWriter sw = new CharArrayWriter(); + copy(reader, sw); + return sw.toString(); + } + + static public long copy(Reader input, Writer output) throws IOException { + char[] buffer = new char[1 << 12]; + long count = 0; + for (int n = 0; (n = input.read(buffer)) >= 0;) { + output.write(buffer, 0, n); + count += n; + } + return count; + } + + /** + * 从输入流读行列表。保证不返回NULL。 + */ + static public List readLines(Reader input) throws IOException { + BufferedReader reader = toBufferedReader(input); + List list = new ArrayList(); + String line = null; + for (;;) { + line = reader.readLine(); + if (null != line) { + list.add(line); + } else { + break; + } + } + return list; + } + + static private BufferedReader toBufferedReader(Reader reader) { + return reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader( + reader); + } + + public static void delete(File fileOrDir) throws IOException { + if (fileOrDir == null) { + return; + } + + if (fileOrDir.isDirectory()) { + cleanDirectory(fileOrDir); + } + + boolean isDeleteOk = fileOrDir.delete(); + if (!isDeleteOk) { + throw new IOException("delete fail"); + } + } + + /** + * 清理目录下的内容 + */ + public static void cleanDirectory(File directory) throws IOException { + if (!directory.exists()) { + String message = directory + " does not exist"; + throw new IllegalArgumentException(message); + } + + if (!directory.isDirectory()) { + String message = directory + " is not a directory"; + throw new IllegalArgumentException(message); + } + + File[] files = directory.listFiles(); + /** + * null if security restricted + */ + if (files == null) { + throw new IOException("Failed to list contents of " + directory); + } + + IOException exception = null; + for (File file : files) { + try { + delete(file); + } catch (IOException ioe) { + exception = ioe; + } + } + + if (null != exception) { + throw exception; + } + } + + public static void writeStringToFile(File file, String data, String encoding) + throws IOException { + OutputStream os = null; + try { + os = new FileOutputStream(file); + os.write(data.getBytes(encoding)); + } finally { + if (null != os) { + os.close(); + } + } + } +} + diff --git a/client/src/main/java/com/alibaba/nacos/client/config/utils/JVMUtil.java b/client/src/main/java/com/alibaba/nacos/client/config/utils/JVMUtil.java new file mode 100644 index 00000000000..7f6ed199ff3 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/utils/JVMUtil.java @@ -0,0 +1,49 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.config.utils; + +import com.alibaba.nacos.client.logger.Logger; + +/** + * Get jvm config + * + * @author Nacos + * + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public class JVMUtil { + + /** + * whether is multi instance + * + * @return whether multi + */ + public static Boolean isMultiInstance() { + return isMultiInstance; + } + + private static Boolean isMultiInstance = false; + private static String TRUE = "true"; + static final public Logger log = LogUtils.logger(JVMUtil.class); + + static { + String multiDeploy = System.getProperty("isMultiInstance", "false"); + if (TRUE.equals(multiDeploy)) { + isMultiInstance = true; + } + log.info("isMultiInstance:{}", isMultiInstance); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/utils/LogUtils.java b/client/src/main/java/com/alibaba/nacos/client/config/utils/LogUtils.java new file mode 100644 index 00000000000..96fa2184a13 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/utils/LogUtils.java @@ -0,0 +1,59 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.config.utils; + +import com.alibaba.nacos.client.config.common.Constants; +import com.alibaba.nacos.client.logger.Level; +import com.alibaba.nacos.client.logger.Logger; +import com.alibaba.nacos.client.logger.LoggerFactory; + +/** + * Log Util + * + * @author Nacos + * + */ +public class LogUtils { + + static int JM_LOG_RETAIN_COUNT = 7; + static String JM_LOG_FILE_SIZE = "10MB"; + + static { + String tmp = "7"; + try { + /** + * change timeout from 100 to 200 + */ + tmp = System.getProperty("JM.LOG.RETAIN.COUNT","7"); + JM_LOG_RETAIN_COUNT = Integer.parseInt(tmp); + } catch (NumberFormatException e) { + e.printStackTrace(); + throw e; + } + + JM_LOG_FILE_SIZE = System.getProperty("JM.LOG.FILE.SIZE","10MB"); + + // logger init + Logger logger = LoggerFactory.getLogger("com.alibaba.nacos.client.config"); + logger.setLevel(Level.INFO); + logger.setAdditivity(false); + logger.activateAppenderWithSizeRolling("nacos", "config.log", Constants.ENCODE, JM_LOG_FILE_SIZE, JM_LOG_RETAIN_COUNT); + } + + public static Logger logger(Class clazz) { + return LoggerFactory.getLogger(clazz); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/utils/MD5.java b/client/src/main/java/com/alibaba/nacos/client/config/utils/MD5.java new file mode 100644 index 00000000000..11efbf3a1c4 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/utils/MD5.java @@ -0,0 +1,145 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.config.utils; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.locks.ReentrantLock; + +import com.alibaba.nacos.client.config.common.Constants; + +/** + * MD5 util + * + * @author Nacos + * + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public class MD5 { + private static int DIGITS_SIZE = 16; + private static char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + private static Map rDigits = new HashMap(16); + static { + for (int i = 0; i < digits.length; ++i) { + rDigits.put(digits[i], i); + } + } + + private static MD5 me = new MD5(); + private MessageDigest mHasher; + private ReentrantLock opLock = new ReentrantLock(); + + + private MD5() { + try { + mHasher = MessageDigest.getInstance("md5"); + } + catch (Exception e) { + throw new RuntimeException(e); + } + + } + + + public static MD5 getInstance() { + return me; + } + + + public String getMD5String(String content) { + return bytes2string(hash(content)); + } + + + public String getMD5String(byte[] content) { + return bytes2string(hash(content)); + } + + + public byte[] getMD5Bytes(byte[] content) { + return hash(content); + } + + + /** + * 对字符串进行md5 + * + * @param str + * @return md5 byte[16] + */ + public byte[] hash(String str) { + opLock.lock(); + try { + byte[] bt = mHasher.digest(str.getBytes(Constants.ENCODE)); + if (null == bt || bt.length != DIGITS_SIZE) { + throw new IllegalArgumentException("md5 need"); + } + return bt; + } + catch (UnsupportedEncodingException e) { + throw new RuntimeException("unsupported utf-8 encoding", e); + } + finally { + opLock.unlock(); + } + } + + + /** + * 对二进制数据进行md5 + * + * @param str + * @return md5 byte[16] + */ + public byte[] hash(byte[] data) { + opLock.lock(); + try { + byte[] bt = mHasher.digest(data); + if (null == bt || bt.length != DIGITS_SIZE) { + throw new IllegalArgumentException("md5 need"); + } + return bt; + } + finally { + opLock.unlock(); + } + } + + + /** + * 将一个字节数组转化为可见的字符串 + * + * @param bt + * @return + */ + public String bytes2string(byte[] bt) { + int l = bt.length; + + char[] out = new char[l << 1]; + + for (int i = 0, j = 0; i < l; i++) { + out[j++] = digits[(0xF0 & bt[i]) >>> 4]; + out[j++] = digits[0x0F & bt[i]]; + } + + return new String(out); + } + + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/utils/ParamUtils.java b/client/src/main/java/com/alibaba/nacos/client/config/utils/ParamUtils.java new file mode 100644 index 00000000000..397ebbf47a7 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/utils/ParamUtils.java @@ -0,0 +1,154 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.config.utils; + +import java.util.List; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.client.utils.IPUtil; +import com.alibaba.nacos.client.utils.StringUtils; + +/** + * Param check util + * + * @author Nacos + * + */ +public class ParamUtils { + + private static char[] validChars = new char[] { '_', '-', '.', ':' }; + + + /** + * 白名单的方式检查, 合法的参数只能包含字母、数字、以及validChars中的字符, 并且不能为空 + * + * @param param + * @return + */ + public static boolean isValid(String param) { + if (param == null) { + return false; + } + int length = param.length(); + for (int i = 0; i < length; i++) { + char ch = param.charAt(i); + if (Character.isLetterOrDigit(ch)) { + continue; + } + else if (isValidChar(ch)) { + continue; + } + else { + return false; + } + } + return true; + } + + + private static boolean isValidChar(char ch) { + for (char c : validChars) { + if (c == ch) { + return true; + } + } + return false; + } + + public static void checkKeyParam(String dataId, String group) throws NacosException { + if (StringUtils.isBlank(dataId) || !ParamUtils.isValid(dataId)) { + throw new NacosException(NacosException.CLIENT_INVALID_PARAM, "dataId invalid"); + } + if (StringUtils.isBlank(group) || !ParamUtils.isValid(group)) { + throw new NacosException(NacosException.CLIENT_INVALID_PARAM, "group invalid"); + } + } + + public static void checkTDG(String tenant, String dataId, String group) throws NacosException { + checkTenant(tenant); + if (StringUtils.isBlank(dataId) || !ParamUtils.isValid(dataId)) { + throw new NacosException(NacosException.CLIENT_INVALID_PARAM, "dataId invalid"); + } + if (StringUtils.isBlank(group) || !ParamUtils.isValid(group)) { + throw new NacosException(NacosException.CLIENT_INVALID_PARAM, "group invalid"); + } + } + + public static void checkKeyParam(String dataId, String group, String datumId) + throws NacosException { + if (StringUtils.isBlank(dataId) || !ParamUtils.isValid(dataId)) { + throw new NacosException(NacosException.CLIENT_INVALID_PARAM, "dataId invalid"); + } + if (StringUtils.isBlank(group) || !ParamUtils.isValid(group)) { + throw new NacosException(NacosException.CLIENT_INVALID_PARAM, "group invalid"); + } + if (StringUtils.isBlank(datumId) || !ParamUtils.isValid(datumId)) { + throw new NacosException(NacosException.CLIENT_INVALID_PARAM, "datumId invalid"); + } + } + + public static void checkKeyParam(List dataIds, String group) throws NacosException { + if (dataIds == null || dataIds.size() == 0) { + throw new NacosException(NacosException.CLIENT_INVALID_PARAM, "dataIds invalid"); + } + for (String dataId : dataIds) { + if (StringUtils.isBlank(dataId) || !ParamUtils.isValid(dataId)) { + throw new NacosException(NacosException.CLIENT_INVALID_PARAM, "dataId invalid"); + } + } + if (StringUtils.isBlank(group) || !ParamUtils.isValid(group)) { + throw new NacosException(NacosException.CLIENT_INVALID_PARAM, "group invalid"); + } + } + + public static void checkParam(String dataId, String group, String content) throws NacosException { + checkKeyParam(dataId, group); + if (StringUtils.isBlank(content)) { + throw new NacosException(NacosException.CLIENT_INVALID_PARAM, "content invalid"); + } + } + + public static void checkParam(String dataId, String group, String datumId, String content) throws NacosException { + checkKeyParam(dataId, group, datumId); + if (StringUtils.isBlank(content)) { + throw new NacosException(NacosException.CLIENT_INVALID_PARAM, "content invalid"); + } + } + + public static void checkTenant(String tenant) throws NacosException { + if (StringUtils.isBlank(tenant) || !ParamUtils.isValid(tenant)) { + throw new NacosException(NacosException.CLIENT_INVALID_PARAM, "tenant invalid"); + } + } + + public static void checkBetaIps(String betaIps) throws NacosException { + if (StringUtils.isBlank(betaIps)) { + throw new NacosException(NacosException.CLIENT_INVALID_PARAM, "betaIps invalid"); + } + String[] ipsArr = betaIps.split(","); + for (String ip : ipsArr) { + if (!IPUtil.isIPV4(ip)) { + throw new NacosException(NacosException.CLIENT_INVALID_PARAM, "betaIps invalid"); + } + } + } + + public static void checkContent(String content) throws NacosException { + if (StringUtils.isBlank(content)) { + throw new NacosException(NacosException.CLIENT_INVALID_PARAM, "content invalid"); + } + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/utils/SnapShotSwitch.java b/client/src/main/java/com/alibaba/nacos/client/config/utils/SnapShotSwitch.java new file mode 100644 index 00000000000..bba324cb561 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/utils/SnapShotSwitch.java @@ -0,0 +1,42 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.config.utils; + +import com.alibaba.nacos.client.config.impl.LocalConfigInfoProcessor; + +/** + * Snapshot switch + * + * @author Nacos + * + */ +public class SnapShotSwitch { + + /** + * whether use local cache + */ + private static Boolean isSnapShot = true; + + public static Boolean getIsSnapShot() { + return isSnapShot; + } + + public static void setIsSnapShot(Boolean isSnapShot) { + SnapShotSwitch.isSnapShot = isSnapShot; + LocalConfigInfoProcessor.cleanAllSnapshot(); + } + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/utils/TenantUtil.java b/client/src/main/java/com/alibaba/nacos/client/config/utils/TenantUtil.java new file mode 100644 index 00000000000..eb7cac9f5c1 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/config/utils/TenantUtil.java @@ -0,0 +1,44 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.config.utils; + +import com.alibaba.nacos.client.utils.StringUtils; + +/** + * Tenant Util + * + * @author Nacos + * + */ +public class TenantUtil { + + static String userTenant = ""; + + static { + userTenant = System.getProperty("tenant.id", ""); + if (StringUtils.isBlank(userTenant)) { + userTenant = System.getProperty("acm.namespace", ""); + } + } + + public static String getUserTenant() { + return userTenant; + } + + public static void setUserTenant(String userTenant) { + TenantUtil.userTenant = userTenant; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/identify/Base64.java b/client/src/main/java/com/alibaba/nacos/client/identify/Base64.java new file mode 100644 index 00000000000..57877a30fb4 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/identify/Base64.java @@ -0,0 +1,707 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.identify; +/* + * 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. + */ + +import java.io.UnsupportedEncodingException; + +/** +* Provides Base64 encoding and decoding as defined by RFC 2045. +* +*

+* This class implements section 6.8. Base64 Content-Transfer-Encoding from RFC 2045 Multipurpose +* Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies by Freed and Borenstein. +*

+*

+* The class can be parameterized in the following manner with various constructors: +*

    +*
  • URL-safe mode: Default off.
  • +*
  • Line length: Default 76. Line length that aren't multiples of 4 will still essentially end up being multiples of +* 4 in the encoded data. +*
  • Line separator: Default is CRLF ("\r\n")
  • +*
+*

+*

+* Since this class operates directly on byte streams, and not character streams, it is hard-coded to only encode/decode +* character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, UTF-8, etc). +*

+*

+* This class is not thread-safe. Each thread should use its own instance. +*

+* +* @see RFC 2045 +* @author Apache Software Foundation +* @since 1.0 +* @version $Revision: 1080712 $ +*/ +public class Base64 { + + /** + * BASE32 characters are 6 bits in length. + * They are formed by taking a block of 3 octets to form a 24-bit string, + * which is converted into 4 BASE64 characters. + */ + private static final int BITS_PER_ENCODED_BYTE = 6; + private static final int BYTES_PER_UNENCODED_BLOCK = 3; + private static final int BYTES_PER_ENCODED_BLOCK = 4; + + /** + * Chunk separator per RFC 2045 section 2.1. + * + *

+ * N.B. The next major release may break compatibility and make this field private. + *

+ * + * @see RFC 2045 section 2.1 + */ + static final byte[] CHUNK_SEPARATOR = {'\r', '\n'}; + + /** + * This array is a lookup table that translates 6-bit positive integer index values into their "Base64 Alphabet" + * equivalents as specified in Table 1 of RFC 2045. + * + * Thanks to "commons" project in ws.apache.org for this code. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + */ + private static final byte[] STANDARD_ENCODE_TABLE = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' + }; + + /** + * This is a copy of the STANDARD_ENCODE_TABLE above, but with + and / + * changed to - and _ to make the encoded Base64 results more URL-SAFE. + * This table is only used when the Base64's mode is set to URL-SAFE. + */ + private static final byte[] URL_SAFE_ENCODE_TABLE = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' + }; + + /** + * This array is a lookup table that translates Unicode characters drawn from the "Base64 Alphabet" (as specified in + * Table 1 of RFC 2045) into their 6-bit positive integer equivalents. Characters that are not in the Base64 + * alphabet but fall within the bounds of the array are translated to -1. + * + * Note: '+' and '-' both decode to 62. '/' and '_' both decode to 63. This means decoder seamlessly handles both + * URL_SAFE and STANDARD base64. (The encoder, on the other hand, needs to know ahead of time what to emit). + * + * Thanks to "commons" project in ws.apache.org for this code. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + */ + private static final byte[] DECODE_TABLE = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, 52, 53, 54, + 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, -1, -1, -1, -1, 63, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 + }; + + /** + * Base64 uses 6-bit fields. + */ + /** Mask used to extract 6 bits, used when encoding */ + private static final int MASK_6BITS = 0x3f; + + // The static final fields above are used for the original static byte[] methods on Base64. + // The private member fields below are used with the new streaming approach, which requires + // some state be preserved between calls of encode() and decode(). + + /** + * Encode table to use: either STANDARD or URL_SAFE. Note: the DECODE_TABLE above remains static because it is able + * to decode both STANDARD and URL_SAFE streams, but the encodeTable must be a member variable so we can switch + * between the two modes. + */ + private final byte[] encodeTable; + + /** + * Only one decode table currently; keep for consistency with Base32 code + */ + private final byte[] decodeTable = DECODE_TABLE; + + /** + * Line separator for encoding. Not used when decoding. Only used if lineLength > 0. + */ + private final byte[] lineSeparator; + + /** + * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. + * decodeSize = 3 + lineSeparator.length; + */ + private final int decodeSize; + + /** + * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. + * encodeSize = 4 + lineSeparator.length; + */ + private final int encodeSize; + + /** + * Place holder for the bytes we're dealing with for our based logic. + * Bitwise operations store and extract the encoding or decoding from this variable. + */ + private int bitWorkArea; + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. + *

+ * When encoding the line length is 0 (no chunking), and the encoding table is STANDARD_ENCODE_TABLE. + *

+ * + *

+ * When decoding all variants are supported. + *

+ */ + public Base64() { + this(0, CHUNK_SEPARATOR, false); + } + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. + *

+ * When encoding the line length and line separator are given in the constructor, and the encoding table is + * STANDARD_ENCODE_TABLE. + *

+ *

+ * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. + *

+ *

+ * When decoding all variants are supported. + *

+ * + * @param lineLength + * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of 4). + * If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when decoding. + * @param lineSeparator + * Each line of encoded data will end with this sequence of bytes. + * @param urlSafe + * Instead of emitting '+' and '/' we emit '-' and '_' respectively. urlSafe is only applied to encode + * operations. Decoding seamlessly handles both modes. + * @throws IllegalArgumentException + * The provided lineSeparator included some base64 characters. That's not going to work! + * @since 1.4 + */ + public Base64(int lineLength, byte[] lineSeparator, boolean urlSafe) { + chunkSeparatorLength = lineSeparator == null ? 0 : lineSeparator.length; + unencodedBlockSize = BYTES_PER_UNENCODED_BLOCK; + encodedBlockSize = BYTES_PER_ENCODED_BLOCK; + this.lineLength = (lineLength > 0 && chunkSeparatorLength > 0) ? (lineLength / encodedBlockSize) * encodedBlockSize : 0; + // TODO could be simplified if there is no requirement to reject invalid line sep when length <=0 + // @see test case Base64Test.testConstructors() + if (lineSeparator != null) { + if (containsAlphabetOrPad(lineSeparator)) { + String sep = null; + try { + sep = new String(lineSeparator, "UTF-8"); + } catch (UnsupportedEncodingException e) { + } + throw new IllegalArgumentException("lineSeparator must not contain base64 characters: [" + sep + "]"); + } + if (lineLength > 0){ + this.encodeSize = BYTES_PER_ENCODED_BLOCK + lineSeparator.length; + this.lineSeparator = new byte[lineSeparator.length]; + System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, lineSeparator.length); + } else { + this.encodeSize = BYTES_PER_ENCODED_BLOCK; + this.lineSeparator = null; + } + } else { + this.encodeSize = BYTES_PER_ENCODED_BLOCK; + this.lineSeparator = null; + } + this.decodeSize = this.encodeSize - 1; + this.encodeTable = urlSafe ? URL_SAFE_ENCODE_TABLE : STANDARD_ENCODE_TABLE; + } + + /** + *

+ * Encodes all of the provided data, starting at inPos, for inAvail bytes. Must be called at least twice: once with + * the data to encode, and once with inAvail set to "-1" to alert encoder that EOF has been reached, so flush last + * remaining bytes (if not multiple of 3). + *

+ *

+ * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + *

+ * + * @param in + * byte[] array of binary data to base64 encode. + * @param inPos + * Position to start reading data from. + * @param inAvail + * Amount of bytes available from input for encoding. + */ + void encode(byte[] in, int inPos, int inAvail) { + if (eof) { + return; + } + if (inAvail < 0) { + eof = true; + if (0 == modulus && lineLength == 0) { + return; + } + ensureBufferSize(encodeSize); + int savedPos = pos; + switch (modulus) { + case 1 : + buffer[pos++] = encodeTable[(bitWorkArea >> 2) & MASK_6BITS]; + buffer[pos++] = encodeTable[(bitWorkArea << 4) & MASK_6BITS]; + + if (encodeTable == STANDARD_ENCODE_TABLE) { + buffer[pos++] = PAD; + buffer[pos++] = PAD; + } + break; + + case 2 : + buffer[pos++] = encodeTable[(bitWorkArea >> 10) & MASK_6BITS]; + buffer[pos++] = encodeTable[(bitWorkArea >> 4) & MASK_6BITS]; + buffer[pos++] = encodeTable[(bitWorkArea << 2) & MASK_6BITS]; + + if (encodeTable == STANDARD_ENCODE_TABLE) { + buffer[pos++] = PAD; + } + break; + default: + break; + } + currentLinePos += pos - savedPos; + /** + * if currentPos == 0 we are at the start of a line, so don't add CRLF + */ + if (lineLength > 0 && currentLinePos > 0) { + System.arraycopy(lineSeparator, 0, buffer, pos, lineSeparator.length); + pos += lineSeparator.length; + } + } else { + for (int i = 0; i < inAvail; i++) { + ensureBufferSize(encodeSize); + modulus = (modulus+1) % BYTES_PER_UNENCODED_BLOCK; + int b = in[inPos++]; + if (b < 0) { + b += 256; + } + bitWorkArea = (bitWorkArea << 8) + b; + if (0 == modulus) { + buffer[pos++] = encodeTable[(bitWorkArea >> 18) & MASK_6BITS]; + buffer[pos++] = encodeTable[(bitWorkArea >> 12) & MASK_6BITS]; + buffer[pos++] = encodeTable[(bitWorkArea >> 6) & MASK_6BITS]; + buffer[pos++] = encodeTable[bitWorkArea & MASK_6BITS]; + currentLinePos += BYTES_PER_ENCODED_BLOCK; + if (lineLength > 0 && lineLength <= currentLinePos) { + System.arraycopy(lineSeparator, 0, buffer, pos, lineSeparator.length); + pos += lineSeparator.length; + currentLinePos = 0; + } + } + } + } + } + + /** + *

+ * Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once + * with the data to decode, and once with inAvail set to "-1" to alert decoder that EOF has been reached. The "-1" + * call is not necessary when decoding, but it doesn't hurt, either. + *

+ *

+ * Ignores all non-base64 characters. This is how chunked (e.g. 76 character) data is handled, since CR and LF are + * silently ignored, but has implications for other bytes, too. This method subscribes to the garbage-in, + * garbage-out philosophy: it will not check the provided data for validity. + *

+ *

+ * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + *

+ * + * @param in + * byte[] array of ascii data to base64 decode. + * @param inPos + * Position to start reading data from. + * @param inAvail + * Amount of bytes available from input for encoding. + */ + void decode(byte[] in, int inPos, int inAvail) { + if (eof) { + return; + } + if (inAvail < 0) { + eof = true; + } + for (int i = 0; i < inAvail; i++) { + ensureBufferSize(decodeSize); + byte b = in[inPos++]; + if (b == PAD) { + // We're done. + eof = true; + break; + } else { + if (b >= 0 && b < DECODE_TABLE.length) { + int result = DECODE_TABLE[b]; + if (result >= 0) { + modulus = (modulus+1) % BYTES_PER_ENCODED_BLOCK; + bitWorkArea = (bitWorkArea << BITS_PER_ENCODED_BYTE) + result; + if (modulus == 0) { + buffer[pos++] = (byte) ((bitWorkArea >> 16) & MASK_8BITS); + buffer[pos++] = (byte) ((bitWorkArea >> 8) & MASK_8BITS); + buffer[pos++] = (byte) (bitWorkArea & MASK_8BITS); + } + } + } + } + } + + // Two forms of EOF as far as base64 decoder is concerned: actual + // EOF (-1) and first time '=' character is encountered in stream. + // This approach makes the '=' padding characters completely optional. + if (eof && modulus != 0) { + ensureBufferSize(decodeSize); + + // We have some spare bits remaining + // Output all whole multiples of 8 bits and ignore the rest + switch (modulus) { + // case 1: // 6 bits - ignore entirely + // break; + case 2 : + bitWorkArea = bitWorkArea >> 4; + buffer[pos++] = (byte) ((bitWorkArea) & MASK_8BITS); + break; + case 3 : + bitWorkArea = bitWorkArea >> 2; + buffer[pos++] = (byte) ((bitWorkArea >> 8) & MASK_8BITS); + buffer[pos++] = (byte) ((bitWorkArea) & MASK_8BITS); + break; + default: + break; + } + } + } + + /** + * Encodes binary data using the base64 algorithm but does not chunk the output. + * + * @param binaryData + * binary data to encode + * @return byte[] containing Base64 characters in their UTF-8 representation. + */ + public static byte[] encodeBase64(byte[] binaryData) { + return encodeBase64(binaryData, false, false, Integer.MAX_VALUE); + } + + /** + * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. + * + * @param binaryData + * Array containing binary data to encode. + * @param isChunked + * if true this encoder will chunk the base64 output into 76 character blocks + * @param urlSafe + * if true this encoder will emit - and _ instead of the usual + and / characters. + * @param maxResultSize + * The maximum result size to accept. + * @return Base64-encoded data. + * @throws IllegalArgumentException + * Thrown when the input array needs an output array bigger than maxResultSize + * @since 1.4 + */ + public static byte[] encodeBase64(byte[] binaryData, boolean isChunked, boolean urlSafe, int maxResultSize) { + if (binaryData == null || binaryData.length == 0) { + return binaryData; + } + + // Create this so can use the super-class method + // Also ensures that the same roundings are performed by the ctor and the code + Base64 b64 = isChunked ? new Base64(MIME_CHUNK_SIZE, CHUNK_SEPARATOR, urlSafe) : new Base64(0, CHUNK_SEPARATOR, urlSafe); + long len = b64.getEncodedLength(binaryData); + if (len > maxResultSize) { + throw new IllegalArgumentException("Input array too big, the output array would be bigger (" + + len + + ") than the specified maximum size of " + + maxResultSize); + } + + return b64.encode(binaryData); + } + + + + /** + * Decodes Base64 data into octets + * + * @param base64Data + * Byte array containing Base64 data + * @return Array containing decoded data. + */ + public static byte[] decodeBase64(byte[] base64Data) { + return new Base64().decode(base64Data); + } + + + /** + * Returns whether or not the octet is in the Base32 alphabet. + * + * @param octet + * The value to test + * @return true if the value is defined in the the Base32 alphabet false otherwise. + */ + protected boolean isInAlphabet(byte octet) { + return octet >= 0 && octet < decodeTable.length && decodeTable[octet] != -1; + } + + /** + * Below from base class + */ + + /** + * MIME chunk size per RFC 2045 section 6.8. + * + *

+ * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any + * equal signs. + *

+ * + * @see RFC 2045 section 6.8 + */ + private static final int MIME_CHUNK_SIZE = 76; + + private static final int DEFAULT_BUFFER_RESIZE_FACTOR = 2; + + /** + * Defines the default buffer size - currently {@value} + * - must be large enough for at least one encoded block+separator + */ + private static final int DEFAULT_BUFFER_SIZE = 8192; + + /** Mask used to extract 8 bits, used in decoding bytes */ + private static final int MASK_8BITS = 0xff; + + /** + * Byte used to pad output. + */ + private static final byte PAD_DEFAULT = '='; + + private static final byte PAD = PAD_DEFAULT; + + /** Number of bytes in each full block of unencoded data, e.g. 4 for Base64 and 5 for Base32 */ + private final int unencodedBlockSize; + + /** Number of bytes in each full block of encoded data, e.g. 3 for Base64 and 8 for Base32 */ + private final int encodedBlockSize; + + /** + * Chunksize for encoding. Not used when decoding. + * A value of zero or less implies no chunking of the encoded data. + * Rounded down to nearest multiple of encodedBlockSize. + */ + private final int lineLength; + + /** + * Size of chunk separator. Not used unless {@link #lineLength} > 0. + */ + private final int chunkSeparatorLength; + + /** + * Buffer for streaming. + */ + private byte[] buffer; + + /** + * Position where next character should be written in the buffer. + */ + private int pos; + + /** + * Position where next character should be read from the buffer. + */ + private int readPos; + + /** + * Boolean flag to indicate the EOF has been reached. Once EOF has been reached, this object becomes useless, + * and must be thrown away. + */ + private boolean eof; + + /** + * Variable tracks how many characters have been written to the current line. Only used when encoding. We use it to + * make sure each encoded line never goes beyond lineLength (if lineLength > 0). + */ + private int currentLinePos; + + /** + * Writes to the buffer only occur after every 3/5 reads when encoding, and every 4/8 reads when decoding. + * This variable helps track that. + */ + private int modulus; + + /** + * Ensure that the buffer has room for size bytes + * + * @param size minimum spare space required + */ + private void ensureBufferSize(int size){ + if ((buffer == null) || (buffer.length < pos + size)){ + if (buffer == null) { + buffer = new byte[DEFAULT_BUFFER_SIZE]; + pos = 0; + readPos = 0; + } else { + byte[] b = new byte[buffer.length * DEFAULT_BUFFER_RESIZE_FACTOR]; + System.arraycopy(buffer, 0, b, 0, buffer.length); + buffer = b; + } + } + } + + /** + * Extracts buffered data into the provided byte[] array, starting at position bPos, + * up to a maximum of bAvail bytes. Returns how many bytes were actually extracted. + * + * @param b + * byte[] array to extract the buffered data into. + * @param bPos + * position in byte[] array to start extraction at. + * @param bAvail + * amount of bytes we're allowed to extract. We may extract fewer (if fewer are available). + * @return The number of bytes successfully extracted into the provided byte[] array. + */ + private int readResults(byte[] b, int bPos, int bAvail) { + if (buffer != null) { + int len = Math.min(pos - readPos, bAvail); + System.arraycopy(buffer, readPos, b, bPos, len); + readPos += len; + if (readPos >= pos) { + buffer = null; + } + return len; + } + return eof ? -1 : 0; + } + + /** + * Resets this object to its initial newly constructed state. + */ + private void reset() { + buffer = null; + pos = 0; + readPos = 0; + currentLinePos = 0; + modulus = 0; + eof = false; + } + + /** + * Decodes a byte[] containing characters in the Base-N alphabet. + * + * @param pArray + * A byte array containing Base-N character data + * @return a byte array containing binary data + */ + private byte[] decode(byte[] pArray) { + reset(); + if (pArray == null || pArray.length == 0) { + return pArray; + } + decode(pArray, 0, pArray.length); + decode(pArray, 0, -1); + byte[] result = new byte[pos]; + readResults(result, 0, result.length); + return result; + } + + /** + * Encodes a byte[] containing binary data, into a byte[] containing characters in the alphabet. + * + * @param pArray + * a byte array containing binary data + * @return A byte array containing only the basen alphabetic character data + */ + private byte[] encode(byte[] pArray) { + reset(); + if (pArray == null || pArray.length == 0) { + return pArray; + } + encode(pArray, 0, pArray.length); + encode(pArray, 0, -1); + byte[] buf = new byte[pos - readPos]; + readResults(buf, 0, buf.length); + return buf; + } + + /** + * Tests a given byte array to see if it contains any characters within the alphabet or PAD. + * + * Intended for use in checking line-ending arrays + * + * @param arrayOctet + * byte array to test + * @return true if any byte is a valid character in the alphabet or PAD; false otherwise + */ + private boolean containsAlphabetOrPad(byte[] arrayOctet) { + if (arrayOctet == null) { + return false; + } + for (int i = 0; i < arrayOctet.length; i++) { + if (PAD == arrayOctet[i] || isInAlphabet(arrayOctet[i])) { + return true; + } + } + return false; + } + + /** + * Calculates the amount of space needed to encode the supplied array. + * + * @param pArray byte[] array which will later be encoded + * + * @return amount of space needed to encoded the supplied array. + * Returns a long since a max-len array will require > Integer.MAX_VALUE + */ + private long getEncodedLength(byte[] pArray) { + // Calculate non-chunked size - rounded up to allow for padding + // cast to long is needed to avoid possibility of overflow + long len = ((pArray.length + unencodedBlockSize-1) / unencodedBlockSize) * (long) encodedBlockSize; + if (lineLength > 0) { + /** + * Round up to nearest multiple + */ + len += ((len + lineLength-1) / lineLength) * chunkSeparatorLength; + } + return len; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/identify/Constants.java b/client/src/main/java/com/alibaba/nacos/client/identify/Constants.java new file mode 100644 index 00000000000..2f566f7549f --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/identify/Constants.java @@ -0,0 +1,47 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.identify; + +/** + * Identify Constants + * + * @author Nacos + * + */ +public class Constants { + public static final String ACCESS_KEY = "accessKey"; + + public static final String SECRET_KEY = "secretKey"; + + public static final String PROPERTIES_FILENAME = "spas.properties"; + + public static final String CREDENTIAL_PATH = "/home/admin/.spas_key/"; + + public static final String CREDENTIAL_DEFAULT = "default"; + + public static final String DOCKER_CREDENTIAL_PATH = "/etc/instanceInfo"; + + public static final String DOCKER_ACCESS_KEY = "env_spas_accessKey"; + + public static final String DOCKER_SECRET_KEY = "env_spas_secretKey"; + + public static final String ENV_ACCESS_KEY = "spas_accessKey"; + + public static final String ENV_SECRET_KEY = "spas_secretKey"; + + public static final String NO_APP_NAME = ""; + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/identify/CredentialListener.java b/client/src/main/java/com/alibaba/nacos/client/identify/CredentialListener.java new file mode 100644 index 00000000000..943facba928 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/identify/CredentialListener.java @@ -0,0 +1,29 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.identify; + +/** + * Credential Listener + * + * @author Nacos + * + */ +public interface CredentialListener { + /** + * update Credential + */ + public void onUpdateCredential(); +} diff --git a/client/src/main/java/com/alibaba/nacos/client/identify/CredentialService.java b/client/src/main/java/com/alibaba/nacos/client/identify/CredentialService.java new file mode 100644 index 00000000000..cf8f792ca72 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/identify/CredentialService.java @@ -0,0 +1,135 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.identify; + +import com.alibaba.nacos.client.config.utils.LogUtils; +import com.alibaba.nacos.client.logger.Logger; +import com.alibaba.nacos.client.utils.StringUtils; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * Credential Service + * + * @author Nacos + * + */ +public final class CredentialService implements SpasCredentialLoader { + static final public Logger log = LogUtils.logger(CredentialService.class); + private static ConcurrentHashMap instances = new ConcurrentHashMap(); + + private String appName; + private Credentials credentials = new Credentials(); + private CredentialWatcher watcher; + private CredentialListener listener; + + private CredentialService(String appName) { + if (appName == null) { + String value = System.getProperty("project.name"); + if (StringUtils.isNotEmpty(value)) { + appName = value; + } + } + this.appName = appName; + watcher = new CredentialWatcher(appName, this); + } + + + public static CredentialService getInstance() { + return getInstance(null); + } + + public static CredentialService getInstance(String appName) { + String key = appName != null ? appName : Constants.NO_APP_NAME; + CredentialService instance = instances.get(key); + if (instance == null) { + instance = new CredentialService(appName); + CredentialService previous = instances.putIfAbsent(key, instance); + if (previous != null) { + instance = previous; + } + } + return instance; + } + + public static CredentialService freeInstance() { + return freeInstance(null); + } + + public static CredentialService freeInstance(String appName) { + String key = appName != null ? appName : Constants.NO_APP_NAME; + CredentialService instance = instances.remove(key); + if (instance != null) { + instance.free(); + } + return instance; + } + + public void free() { + if (watcher != null) { + watcher.stop(); + } + log.info(appName, this.getClass().getSimpleName() + " is freed"); + } + + public Credentials getCredential() { + Credentials localCredential = credentials; + if (localCredential.valid()) { + return localCredential; + } + return credentials; + } + + public void setCredential(Credentials credential) { + boolean changed = !(credentials == credential || (credentials != null && credentials.identical(credential))); + credentials = credential; + if (changed && listener != null) { + listener.onUpdateCredential(); + } + } + + public void setStaticCredential(Credentials credential) { + if (watcher != null) { + watcher.stop(); + } + setCredential(credential); + } + + public void registerCredentialListener(CredentialListener listener) { + this.listener = listener; + } + + @Deprecated + public void setAccessKey(String accessKey) { + credentials.setAccessKey(accessKey); + } + + @Deprecated + public void setSecretKey(String secretKey) { + credentials.setSecretKey(secretKey); + } + + @Deprecated + public String getAccessKey() { + return credentials.getAccessKey(); + } + + @Deprecated + public String getSecretKey() { + return credentials.getSecretKey(); + } + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/identify/CredentialWatcher.java b/client/src/main/java/com/alibaba/nacos/client/identify/CredentialWatcher.java new file mode 100644 index 00000000000..553849ba948 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/identify/CredentialWatcher.java @@ -0,0 +1,214 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.identify; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Properties; +import java.util.Timer; +import java.util.TimerTask; + +import com.alibaba.nacos.client.config.utils.LogUtils; +import com.alibaba.nacos.client.logger.Logger; +import com.alibaba.nacos.client.utils.StringUtils; + +/** + * Credential Watcher + * + * @author Nacos + * + */ +public class CredentialWatcher { + static final public Logger SpasLogger = LogUtils.logger(CredentialWatcher.class); + private static final long REFRESH_INTERVAL = 10 * 1000; + + private CredentialService serviceInstance; + private String appName; + private String propertyPath; + private TimerTask watcher; + private boolean stopped; + + @SuppressWarnings("PMD.AvoidUseTimerRule") + public CredentialWatcher(String appName, CredentialService serviceInstance) { + this.appName = appName; + this.serviceInstance = serviceInstance; + loadCredential(true); + watcher = new TimerTask() { + private Timer timer = new Timer(true); + private long modified = 0; + + { + timer.schedule(this, REFRESH_INTERVAL, REFRESH_INTERVAL); + } + + @Override + public void run() { + synchronized (this) { + if (stopped) { + return; + } + boolean reload = false; + if (propertyPath == null) { + reload = true; + } else { + File file = new File(propertyPath); + long lastModified = file.lastModified(); + if (modified != lastModified) { + reload = true; + modified = lastModified; + } + } + if (reload) { + loadCredential(false); + } + } + } + }; + } + + public void stop() { + if (stopped) { + return; + } + if (watcher != null) { + synchronized (watcher) { + watcher.cancel(); + stopped = true; + } + } + SpasLogger.info(appName, this.getClass().getSimpleName() + " is stopped"); + } + + private void loadCredential(boolean init) { + boolean logWarn = init; + if (propertyPath == null) { + URL url = ClassLoader.getSystemResource(Constants.PROPERTIES_FILENAME); + if (url != null) { + propertyPath = url.getPath(); + } + if (propertyPath == null || propertyPath.isEmpty()) { + + String value = System.getProperty("spas.identity"); + if (StringUtils.isNotEmpty(value)) { + propertyPath = value; + } + if (propertyPath == null || propertyPath.isEmpty()) { + propertyPath = Constants.CREDENTIAL_PATH + (appName == null ? Constants.CREDENTIAL_DEFAULT : appName); + } + else { + if (logWarn) { + SpasLogger.info(appName, "Defined credential file: -D" + "spas.identity" + "=" + propertyPath); + } + } + } + else { + if (logWarn) { + SpasLogger.info(appName, "Load credential file from classpath: " + Constants.PROPERTIES_FILENAME); + } + } + } + + InputStream propertiesIS = null; + do { + try { + propertiesIS = new FileInputStream(propertyPath); + } catch (FileNotFoundException e) { + if (appName != null && !appName.equals(Constants.CREDENTIAL_DEFAULT) && propertyPath.equals(Constants.CREDENTIAL_PATH + appName)) { + propertyPath = Constants.CREDENTIAL_PATH + Constants.CREDENTIAL_DEFAULT; + continue; + } + if (!Constants.DOCKER_CREDENTIAL_PATH.equals(propertyPath)) { + propertyPath = Constants.DOCKER_CREDENTIAL_PATH; + continue; + } + } + break; + } while (true); + + String accessKey = null; + String secretKey = null; + if (propertiesIS == null) { + propertyPath = null; + accessKey = System.getenv(Constants.ENV_ACCESS_KEY); + secretKey = System.getenv(Constants.ENV_SECRET_KEY); + if (accessKey == null && secretKey == null) { + if (logWarn) { + SpasLogger.info(appName, "No credential found"); + } + return; + } + } + else { + Properties properties = new Properties(); + try { + properties.load(propertiesIS); + } catch (IOException e) { + SpasLogger.error("26", "Unable to load credential file, appName:" + appName + + "Unable to load credential file " + propertyPath, e); + propertyPath = null; + return; + } finally { + try { + propertiesIS.close(); + } catch (IOException e) { + SpasLogger.error("27", "Unable to close credential file, appName:" + appName + + "Unable to close credential file " + propertyPath, e); + } + } + + if (logWarn) { + SpasLogger.info(appName, "Load credential file " + propertyPath); + } + + if (!Constants.DOCKER_CREDENTIAL_PATH.equals(propertyPath)) { + if (properties.containsKey(Constants.ACCESS_KEY)) { + accessKey = properties.getProperty(Constants.ACCESS_KEY); + } + if (properties.containsKey(Constants.SECRET_KEY)) { + secretKey = properties.getProperty(Constants.SECRET_KEY); + } + } else { + if (properties.containsKey(Constants.DOCKER_ACCESS_KEY)) { + accessKey = properties.getProperty(Constants.DOCKER_ACCESS_KEY); + } + if (properties.containsKey(Constants.DOCKER_SECRET_KEY)) { + secretKey = properties.getProperty(Constants.DOCKER_SECRET_KEY); + } + } + } + + if (accessKey != null) { + accessKey = accessKey.trim(); + } + if (secretKey != null) { + secretKey = secretKey.trim(); + } + + Credentials credential = new Credentials(accessKey, secretKey); + if (!credential.valid()) { + SpasLogger.warn("1", "Credential file missing required property" + appName + "Credential file missing " + + Constants.ACCESS_KEY + " or " + Constants.SECRET_KEY); + propertyPath = null; + // return; + } + + serviceInstance.setCredential(credential); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/identify/Credentials.java b/client/src/main/java/com/alibaba/nacos/client/identify/Credentials.java new file mode 100644 index 00000000000..f54100bf6b8 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/identify/Credentials.java @@ -0,0 +1,65 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.identify; + +/** + * Credentials + * + * @author Nacos + * + */ +public class Credentials implements SpasCredential { + + private volatile String accessKey; + + private volatile String secretKey; + + public Credentials(String accessKey, String secretKey) { + this.accessKey = accessKey; + this.secretKey = secretKey; + } + + public Credentials() { + this(null, null); + } + + public String getAccessKey() { + return accessKey; + } + + public void setAccessKey(String accessKey) { + this.accessKey = accessKey; + } + + public String getSecretKey() { + return secretKey; + } + + public void setSecretKey(String secretKey) { + this.secretKey = secretKey; + } + + public boolean valid() { + return accessKey != null && !accessKey.isEmpty() && secretKey != null && !secretKey.isEmpty(); + } + + public boolean identical(Credentials other) { + return this == other || + (other != null && + (accessKey == null && other.accessKey == null || accessKey != null && accessKey.equals(other.accessKey)) && + (secretKey == null && other.secretKey == null || secretKey != null && secretKey.equals(other.secretKey))); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/identify/STSConfig.java b/client/src/main/java/com/alibaba/nacos/client/identify/STSConfig.java new file mode 100644 index 00000000000..e555c535351 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/identify/STSConfig.java @@ -0,0 +1,128 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.identify; + +import com.alibaba.nacos.client.utils.StringUtils; + +/** + * Sts config + * + * @author Nacos + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public class STSConfig { + private static final String RAM_SECURITY_CREDENTIALS_URL + = ""; + private String ramRoleName; + /** + * STS 临时凭证有效期剩余多少时开始刷新(允许本地时间比 STS 服务时间最多慢多久) + */ + private int timeToRefreshInMillisecond = 3 * 60 * 1000; + /** + * 获取 STS 临时凭证的元数据接口(包含角色名称) + */ + private String securityCredentialsUrl; + /** + * 设定 STS 临时凭证,不再通过元数据接口获取 + */ + private String securityCredentials; + /** + * 是否缓存 + */ + private boolean cacheSecurityCredentials = true; + + private static class Singleton { + private static final STSConfig INSTANCE = new STSConfig(); + } + + private STSConfig() { + String ramRoleName = System.getProperty("ram.role.name"); + if (!StringUtils.isBlank(ramRoleName)) { + setRamRoleName(ramRoleName); + } + + String timeToRefreshInMillisecond = System.getProperty("time.to.refresh.in.millisecond"); + if (!StringUtils.isBlank(timeToRefreshInMillisecond)) { + setTimeToRefreshInMillisecond(Integer.parseInt(timeToRefreshInMillisecond)); + } + + String securityCredentials = System.getProperty("security.credentials"); + if (!StringUtils.isBlank(securityCredentials)) { + setSecurityCredentials(securityCredentials); + } + + String securityCredentialsUrl = System.getProperty("security.credentials.url"); + if (!StringUtils.isBlank(securityCredentialsUrl)) { + setSecurityCredentialsUrl(securityCredentialsUrl); + } + + String cacheSecurityCredentials = System.getProperty("cache.security.credentials"); + if (!StringUtils.isBlank(cacheSecurityCredentials)) { + setCacheSecurityCredentials(Boolean.valueOf(cacheSecurityCredentials)); + } + } + + public static STSConfig getInstance() { + return Singleton.INSTANCE; + } + + public String getRamRoleName() { + return ramRoleName; + } + + public void setRamRoleName(String ramRoleName) { + this.ramRoleName = ramRoleName; + } + + public int getTimeToRefreshInMillisecond() { + return timeToRefreshInMillisecond; + } + + public void setTimeToRefreshInMillisecond(int timeToRefreshInMillisecond) { + this.timeToRefreshInMillisecond = timeToRefreshInMillisecond; + } + + public String getSecurityCredentialsUrl() { + if (securityCredentialsUrl == null && ramRoleName != null) { + return RAM_SECURITY_CREDENTIALS_URL + ramRoleName; + } + return securityCredentialsUrl; + } + + public void setSecurityCredentialsUrl(String securityCredentialsUrl) { + this.securityCredentialsUrl = securityCredentialsUrl; + } + + public String getSecurityCredentials() { + return securityCredentials; + } + + public void setSecurityCredentials(String securityCredentials) { + this.securityCredentials = securityCredentials; + } + + public boolean isSTSOn() { + return StringUtils.isNotEmpty(getSecurityCredentials()) || StringUtils.isNotEmpty(getSecurityCredentialsUrl()); + } + + public boolean isCacheSecurityCredentials() { + return cacheSecurityCredentials; + } + + public void setCacheSecurityCredentials(boolean cacheSecurityCredentials) { + this.cacheSecurityCredentials = cacheSecurityCredentials; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/identify/SpasCredential.java b/client/src/main/java/com/alibaba/nacos/client/identify/SpasCredential.java new file mode 100644 index 00000000000..8d6a2bd919f --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/identify/SpasCredential.java @@ -0,0 +1,38 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.identify; + +/** + * Spas Credential Interface + * + * @author Nacos + * + */ +public interface SpasCredential { + /** + * get AccessKey + * + * @return AccessKey + */ + public String getAccessKey(); + + /** + * get SecretKey + * + * @return SecretKey + */ + public String getSecretKey(); +} diff --git a/client/src/main/java/com/alibaba/nacos/client/identify/SpasCredentialLoader.java b/client/src/main/java/com/alibaba/nacos/client/identify/SpasCredentialLoader.java new file mode 100644 index 00000000000..12012ee2a21 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/identify/SpasCredentialLoader.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.identify; + +/** + * Spas Credential Loader + * + * @author Nacos + * + */ +public interface SpasCredentialLoader { + /** + * get Credential + * + * @return Credential + */ + SpasCredential getCredential(); +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/Level.java b/client/src/main/java/com/alibaba/nacos/client/logger/Level.java new file mode 100644 index 00000000000..38522f80113 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/Level.java @@ -0,0 +1,48 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger; + +/** + * 阿里中间件日志级别 + * + * @author zhuyong 2014年3月20日 上午9:57:27 + */ +public enum Level { + /** + * log level + */ + DEBUG("DEBUG"), INFO("INFO"), WARN("WARN"), ERROR("ERROR"), OFF("OFF"); + + private String name; + + Level(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + + public static Level codeOf(String level) { + for (Level l : Level.values()) { + if (l.name.equals(level)) { + return l; + } + } + + return OFF; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/Logger.java b/client/src/main/java/com/alibaba/nacos/client/logger/Logger.java new file mode 100644 index 00000000000..f38c3e811f1 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/Logger.java @@ -0,0 +1,238 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger; + +import com.alibaba.nacos.client.logger.option.ActivateOption; + +/** + *
+ * 阿里中间件日志API,用于输出定制化的日志
+ * 
+ * 定制格式如下:01 %d{yyyy-MM-dd HH:mm:ss.SSS} %p [%-5t:%c{2}] %m%n
+ * 其中:
+ * 01                           日志API版本,后续如果格式有变化,会修改此版本号,方便机器解析
+ * d{yyyy-MM-dd HH:mm:ss.SSS}   时间,如,2014-03-19 20:55:08.501,最后面的表示毫秒
+ * %p                           日志级别,如INFO,ERROR
+ * [%-5t:%c{2}]                 线程名:日志名
+ * %m                           日志信息
+ * %n                           换行
+ * 
+ * 关于%m,也有其中的格式要求:[Context] [STAT-INFO] [ERROR-CODE]
+ * 其中:
+ * Context                      打印时间时的上下文信息,如果没有,则内容为空,但'[]'这个占位符仍要输出
+ * STAT-INFO                    待定
+ * ERROR-CODE                   常见的错误码,帮助用户解决问题
+ * 
+ * 在异常中,也需要输出ErrorCode及对应的TraceUrl,可以使用
+ * com.alibaba.nacos.client.logger.support.LoggerHelper.getErrorCodeStr(String errorCode)来获取格式化后的串
+ * 
+ * + * @author zhuyong 2014年3月20日 上午9:58:27 + */ +public interface Logger extends ActivateOption { + + /** + * 输出Debug日志 + * + * @param message 日志信息(当使用ResourceBundle用于国际化日志输出时,message为对应的key, since 0.1.5) + */ + void debug(String message); + + /** + * 输出Debug日志 + * + * @param format 日志信息格式化字符串,比如 'Hi,{} {} {}'(当使用ResourceBundle用于国际化日志输出时,message为对应的key, since 0.1.5) + * @param args 格式化串参数数组 + */ + void debug(String format, Object... args); + + /** + * 输出Debug日志 + * + * @param context 日志上下文信息 + * @param message 日志信息(当使用ResourceBundle用于国际化日志输出时,message为对应的key, since 0.1.5) + */ + void debug(String context, String message); + + /** + * 输出Debug日志 + * + * @param context 日志上下文信息 + * @param format 日志信息格式化字符串,比如 'Hi,{} {} {}'(当使用ResourceBundle用于国际化日志输出时,message为对应的key, since 0.1.5) + * @param args 格式化串参数数组 + */ + void debug(String context, String format, Object... args); + + /** + * 输出Info日志 + * + * @param message 日志信息(当使用ResourceBundle用于国际化日志输出时,message为对应的key, since 0.1.5) + */ + void info(String message); + + /** + * 输出Info日志 + * + * @param format 日志信息格式化字符串,比如 'Hi,{} {} {}'(当使用ResourceBundle用于国际化日志输出时,message为对应的key, since 0.1.5) + * @param args 格式化串参数数组 + */ + void info(String format, Object... args); + + /** + * 输出Info日志 + * + * @param context 日志上下文信息 + * @param message 日志信息(当使用ResourceBundle用于国际化日志输出时,message为对应的key, since 0.1.5) + */ + void info(String context, String message); + + /** + * 输出Info日志 + * + * @param context 日志上下文信息 + * @param format 日志信息格式化字符串,比如 'Hi,{} {} {}'(当使用ResourceBundle用于国际化日志输出时,message为对应的key, since 0.1.5) + * @param args 格式化串参数数组 + */ + void info(String context, String format, Object... args); + + /** + * 输出Warn日志 + * + * @param message 日志信息(当使用ResourceBundle用于国际化日志输出时,message为对应的key, since 0.1.5) + */ + void warn(String message); + + /** + * 输出Warn日志 + * + * @param message 日志信息(当使用ResourceBundle用于国际化日志输出时,message为对应的key, since 0.1.5) + * @param t 异常信息 + * @since 0.1.5 + */ + void warn(String message, Throwable t); + + /** + * 输出Warn日志 + * + * @param format 日志信息格式化字符串,比如 'Hi,{} {} {}'(当使用ResourceBundle用于国际化日志输出时,message为对应的key, since 0.1.5) + * @param args 格式化串参数数组 + */ + void warn(String format, Object... args); + + /** + * 输出Warn日志 + * + * @param context 日志上下文信息 + * @param message 日志信息(当使用ResourceBundle用于国际化日志输出时,message为对应的key, since 0.1.5) + */ + void warn(String context, String message); + + /** + * 输出Warn日志 + * + * @param context 日志上下文信息 + * @param format 日志信息格式化字符串,比如 'Hi,{} {} {}'(当使用ResourceBundle用于国际化日志输出时,message为对应的key, since 0.1.5) + * @param args 格式化串参数数组 + */ + void warn(String context, String format, Object... args); + + /** + * 输出Error日志 + * + * @param errorCode 错误码,如HSF-0001 + * @param message 日志信息(当使用ResourceBundle用于国际化日志输出时,message为对应的key, since 0.1.5) + */ + void error(String errorCode, String message); + + /** + * 输出Error日志 + * + * @param errorCode 错误码,如HSF-0001 + * @param message 日志信息(当使用ResourceBundle用于国际化日志输出时,message为对应的key, since 0.1.5) + * @param t 异常信息 + */ + void error(String errorCode, String message, Throwable t); + + /** + * 输出Error日志 + * + * @param errorCode 错误码,如HSF-0001 + * @param format 日志信息格式化字符串,比如 'Hi,{} {} {}'(当使用ResourceBundle用于国际化日志输出时,message为对应的key, since 0.1.5) + * @param objs 格式化串参数数组 + */ + void error(String errorCode, String format, Object... objs); + + /** + * 输出Error日志 + * + * @param context 日志上下文信息 + * @param errorCode 错误码 + * @param message 日志信息(当使用ResourceBundle用于国际化日志输出时,message为对应的key, since 0.1.5) + */ + void error(String context, String errorCode, String message); + + /** + * 输出Error日志 + * + * @param context 日志上下文信息 + * @param errorCode 错误码 + * @param message 日志信息(当使用ResourceBundle用于国际化日志输出时,message为对应的key, since 0.1.5) + * @param t 异常信息 + */ + void error(String context, String errorCode, String message, Throwable t); + + /** + * 输出Error日志 + * + * @param context 日志上下文信息 + * @param errorCode 错误码 + * @param format 日志信息格式化字符串,比如 'Hi,{} {} {}'(当使用ResourceBundle用于国际化日志输出时,message为对应的key, since 0.1.5) + * @param args 格式化串参数 + */ + void error(String context, String errorCode, String format, Object... args); + + /** + * 判断Debug级别是否开启 + * + * @return Debug级别是否开启 + */ + boolean isDebugEnabled(); + + /** + * 判断Info级别是否开启 + * @return Info级别是否开启 + */ + boolean isInfoEnabled(); + + /** + * 判断Warn级别是否开启 + * @return Warn级别是否开启 + */ + boolean isWarnEnabled(); + + /** + * 判断Error级别是否开启 + * + * @return Error级别是否开启 + */ + boolean isErrorEnabled(); + + /** + * * 获取内部日志实现对象 + * @return 内部日志实现对象 + */ + Object getDelegate(); +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/LoggerFactory.java b/client/src/main/java/com/alibaba/nacos/client/logger/LoggerFactory.java new file mode 100644 index 00000000000..bc421aca797 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/LoggerFactory.java @@ -0,0 +1,100 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.alibaba.nacos.client.logger.log4j.Log4jLoggerFactory; +import com.alibaba.nacos.client.logger.log4j2.Log4j2LoggerFactory; +import com.alibaba.nacos.client.logger.nop.NopLoggerFactory; +import com.alibaba.nacos.client.logger.slf4j.Slf4jLoggerFactory; +import com.alibaba.nacos.client.logger.support.ILoggerFactory; +import com.alibaba.nacos.client.logger.support.LogLog; + +/** + *
+ * 阿里中间件LoggerFactory,获取具体日志实现
+ * 目前支持log4j/log4j2/slf4j/jcl日志门面和log4j/log4j2/logback日志实现:
+ * log4j
+ * log4j2
+ * slf4j + logback
+ * slf4j + slf4j-log4j12 + log4j
+ * slf4j + slf4j-log4j-impl + log4j2
+ * jcl + log4j
+ * jcl + jcl-over-slf4j + slf4j + logback
+ * jcl + jcl-over-slf4j + slf4j + slf4j-log4j-impl + log4j
+ * 查找实现的优先顺序依次为slf4j > log4j > log4j2
+ * 
+ * + * @author zhuyong 2014年3月20日 上午10:17:33 + */ +public class LoggerFactory { + + private LoggerFactory() { + } + + private static volatile ILoggerFactory LOGGER_FACTORY; + private static Map loggerCache; + + // 查找常用的日志框架 + static { + try { + setLoggerFactory(new Slf4jLoggerFactory()); + LogLog.info("Init JM logger with Slf4jLoggerFactory success, " + LoggerFactory.class.getClassLoader()); + } catch (Throwable e1) { + try { + setLoggerFactory(new Log4jLoggerFactory()); + LogLog.info("Init JM logger with Log4jLoggerFactory, " + LoggerFactory.class.getClassLoader()); + } catch (Throwable e2) { + try { + setLoggerFactory(new Log4j2LoggerFactory()); + LogLog.info("Init JM logger with Log4j2LoggerFactory, " + LoggerFactory.class.getClassLoader()); + } catch (Throwable e3) { + setLoggerFactory(new NopLoggerFactory()); + LogLog.warn("Init JM logger with NopLoggerFactory, pay attention. " + + LoggerFactory.class.getClassLoader(), e2); + } + } + } + + loggerCache = new ConcurrentHashMap(); + } + + public static Logger getLogger(String name) { + Logger logger = loggerCache.get(name); + if (logger == null) { + synchronized (LOGGER_FACTORY) { + logger = loggerCache.get(name); + if (logger == null) { + logger = LOGGER_FACTORY.getLogger(name); + loggerCache.put(name, logger); + } + } + } + return logger; + } + + public static Logger getLogger(Class clazz) { + return getLogger(clazz.getName()); + } + + private static void setLoggerFactory(ILoggerFactory loggerFactory) { + if (loggerFactory != null) { + LoggerFactory.LOGGER_FACTORY = loggerFactory; + } + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/json/JSONArray.java b/client/src/main/java/com/alibaba/nacos/client/logger/json/JSONArray.java new file mode 100644 index 00000000000..d14f47e7024 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/json/JSONArray.java @@ -0,0 +1,398 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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. + */ +/* + * $Id: JSONArray.java,v 1.1 2006/04/15 14:10:48 platform Exp $ + * Created on 2006-4-10 + */ +package com.alibaba.nacos.client.logger.json; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; + +/** + * A JSON array. JSONObject supports java.util.List interface. + * + * @author FangYidong + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public class JSONArray extends ArrayList implements JSONAware, JSONStreamAware { + private static final long serialVersionUID = 3957988303675231981L; + + /** + * Constructs an empty JSONArray. + */ + public JSONArray(){ + super(); + } + + /** + * Constructs a JSONArray containing the elements of the specified + * collection, in the order they are returned by the collection's iterator. + * + * @param c the collection whose elements are to be placed into this JSONArray + */ + public JSONArray(Collection c){ + super(c); + } + + /** + * Encode a list into JSON text and write it to out. + * If this list is also a JSONStreamAware or a JSONAware, JSONStreamAware and JSONAware specific behaviours will be ignored at this top level. + * + * @see com.alibaba.nacos.client.logger.json.JSONValue#writeJSONString(Object, Writer) + * + * @param collection + * @param out + */ + public static void writeJSONString(Collection collection, Writer out) throws IOException{ + if(collection == null){ + out.write("null"); + return; + } + + boolean first = true; + Iterator iter=collection.iterator(); + + out.write('['); + while(iter.hasNext()){ + if(first) { + first = false; + } + else { + out.write(','); + } + Object value=iter.next(); + if(value == null){ + out.write("null"); + continue; + } + + JSONValue.writeJSONString(value, out); + } + out.write(']'); + } + + public void writeJSONString(Writer out) throws IOException{ + writeJSONString(this, out); + } + + /** + * Convert a list to JSON text. The result is a JSON array. + * If this list is also a JSONAware, JSONAware specific behaviours will be omitted at this top level. + * + * @see com.alibaba.nacos.client.logger.json.JSONValue#toJSONString(Object) + * + * @param collection + * @return JSON text, or "null" if list is null. + */ + public static String toJSONString(Collection collection){ + final StringWriter writer = new StringWriter(); + + try { + writeJSONString(collection, writer); + return writer.toString(); + } catch(IOException e){ + // This should never happen for a StringWriter + throw new RuntimeException(e); + } + } + + public static void writeJSONString(byte[] array, Writer out) throws IOException{ + if(array == null){ + out.write("null"); + } else if(array.length == 0) { + out.write("[]"); + } else { + out.write("["); + out.write(String.valueOf(array[0])); + + for(int i = 1; i < array.length; i++){ + out.write(","); + out.write(String.valueOf(array[i])); + } + + out.write("]"); + } + } + + public static String toJSONString(byte[] array){ + final StringWriter writer = new StringWriter(); + + try { + writeJSONString(array, writer); + return writer.toString(); + } catch(IOException e){ + // This should never happen for a StringWriter + throw new RuntimeException(e); + } + } + + public static void writeJSONString(short[] array, Writer out) throws IOException{ + if(array == null){ + out.write("null"); + } else if(array.length == 0) { + out.write("[]"); + } else { + out.write("["); + out.write(String.valueOf(array[0])); + + for(int i = 1; i < array.length; i++){ + out.write(","); + out.write(String.valueOf(array[i])); + } + + out.write("]"); + } + } + + public static String toJSONString(short[] array){ + final StringWriter writer = new StringWriter(); + + try { + writeJSONString(array, writer); + return writer.toString(); + } catch(IOException e){ + // This should never happen for a StringWriter + throw new RuntimeException(e); + } + } + + public static void writeJSONString(int[] array, Writer out) throws IOException{ + if(array == null){ + out.write("null"); + } else if(array.length == 0) { + out.write("[]"); + } else { + out.write("["); + out.write(String.valueOf(array[0])); + + for(int i = 1; i < array.length; i++){ + out.write(","); + out.write(String.valueOf(array[i])); + } + + out.write("]"); + } + } + + public static String toJSONString(int[] array){ + final StringWriter writer = new StringWriter(); + + try { + writeJSONString(array, writer); + return writer.toString(); + } catch(IOException e){ + // This should never happen for a StringWriter + throw new RuntimeException(e); + } + } + + public static void writeJSONString(long[] array, Writer out) throws IOException{ + if(array == null){ + out.write("null"); + } else if(array.length == 0) { + out.write("[]"); + } else { + out.write("["); + out.write(String.valueOf(array[0])); + + for(int i = 1; i < array.length; i++){ + out.write(","); + out.write(String.valueOf(array[i])); + } + + out.write("]"); + } + } + + public static String toJSONString(long[] array){ + final StringWriter writer = new StringWriter(); + + try { + writeJSONString(array, writer); + return writer.toString(); + } catch(IOException e){ + // This should never happen for a StringWriter + throw new RuntimeException(e); + } + } + + public static void writeJSONString(float[] array, Writer out) throws IOException{ + if(array == null){ + out.write("null"); + } else if(array.length == 0) { + out.write("[]"); + } else { + out.write("["); + out.write(String.valueOf(array[0])); + + for(int i = 1; i < array.length; i++){ + out.write(","); + out.write(String.valueOf(array[i])); + } + + out.write("]"); + } + } + + public static String toJSONString(float[] array){ + final StringWriter writer = new StringWriter(); + + try { + writeJSONString(array, writer); + return writer.toString(); + } catch(IOException e){ + // This should never happen for a StringWriter + throw new RuntimeException(e); + } + } + + public static void writeJSONString(double[] array, Writer out) throws IOException{ + if(array == null){ + out.write("null"); + } else if(array.length == 0) { + out.write("[]"); + } else { + out.write("["); + out.write(String.valueOf(array[0])); + + for(int i = 1; i < array.length; i++){ + out.write(","); + out.write(String.valueOf(array[i])); + } + + out.write("]"); + } + } + + public static String toJSONString(double[] array){ + final StringWriter writer = new StringWriter(); + + try { + writeJSONString(array, writer); + return writer.toString(); + } catch(IOException e){ + // This should never happen for a StringWriter + throw new RuntimeException(e); + } + } + + public static void writeJSONString(boolean[] array, Writer out) throws IOException{ + if(array == null){ + out.write("null"); + } else if(array.length == 0) { + out.write("[]"); + } else { + out.write("["); + out.write(String.valueOf(array[0])); + + for(int i = 1; i < array.length; i++){ + out.write(","); + out.write(String.valueOf(array[i])); + } + + out.write("]"); + } + } + + public static String toJSONString(boolean[] array){ + final StringWriter writer = new StringWriter(); + + try { + writeJSONString(array, writer); + return writer.toString(); + } catch(IOException e){ + // This should never happen for a StringWriter + throw new RuntimeException(e); + } + } + + public static void writeJSONString(char[] array, Writer out) throws IOException{ + if(array == null){ + out.write("null"); + } else if(array.length == 0) { + out.write("[]"); + } else { + out.write("[\""); + out.write(String.valueOf(array[0])); + + for(int i = 1; i < array.length; i++){ + out.write("\",\""); + out.write(String.valueOf(array[i])); + } + + out.write("\"]"); + } + } + + public static String toJSONString(char[] array){ + final StringWriter writer = new StringWriter(); + + try { + writeJSONString(array, writer); + return writer.toString(); + } catch(IOException e){ + // This should never happen for a StringWriter + throw new RuntimeException(e); + } + } + + public static void writeJSONString(Object[] array, Writer out) throws IOException{ + if(array == null){ + out.write("null"); + } else if(array.length == 0) { + out.write("[]"); + } else { + out.write("["); + JSONValue.writeJSONString(array[0], out); + + for(int i = 1; i < array.length; i++){ + out.write(","); + JSONValue.writeJSONString(array[i], out); + } + + out.write("]"); + } + } + + public static String toJSONString(Object[] array){ + final StringWriter writer = new StringWriter(); + + try { + writeJSONString(array, writer); + return writer.toString(); + } catch(IOException e){ + // This should never happen for a StringWriter + throw new RuntimeException(e); + } + } + + public String toJSONString(){ + return toJSONString(this); + } + + /** + * Returns a string representation of this array. This is equivalent to + * calling {@link JSONArray#toJSONString()}. + */ + public String toString() { + return toJSONString(); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/json/JSONAware.java b/client/src/main/java/com/alibaba/nacos/client/logger/json/JSONAware.java new file mode 100644 index 00000000000..778cdb1586c --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/json/JSONAware.java @@ -0,0 +1,30 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.json; + +/** + * Beans that support customized output of JSON text shall implement this interface. + * @author FangYidong + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public interface JSONAware { + /** + * format change + * + * @return JSON text + */ + String toJSONString(); +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/json/JSONObject.java b/client/src/main/java/com/alibaba/nacos/client/logger/json/JSONObject.java new file mode 100644 index 00000000000..836aa2b69e7 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/json/JSONObject.java @@ -0,0 +1,152 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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. + */ +/* + * $Id: JSONObject.java,v 1.1 2006/04/15 14:10:48 platform Exp $ + * Created on 2006-4-10 + */ +package com.alibaba.nacos.client.logger.json; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * A JSON object. Key value pairs are unordered. JSONObject supports java.util.Map interface. + * + * @author FangYidong + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public class JSONObject extends HashMap implements Map, JSONAware, JSONStreamAware{ + + private static final long serialVersionUID = -503443796854799292L; + + + public JSONObject() { + super(); + } + + /** + * Allows creation of a JSONObject from a Map. After that, both the + * generated JSONObject and the Map can be modified independently. + * + * @param map + */ + public JSONObject(Map map) { + super(map); + } + + + /** + * Encode a map into JSON text and write it to out. + * If this map is also a JSONAware or JSONStreamAware, JSONAware or JSONStreamAware specific behaviours will be ignored at this top level. + * + * @see com.alibaba.nacos.client.logger.json.JSONValue#writeJSONString(Object, Writer) + * + * @param map + * @param out + */ + public static void writeJSONString(Map map, Writer out) throws IOException { + if(map == null){ + out.write("null"); + return; + } + + boolean first = true; + Iterator iter=map.entrySet().iterator(); + + out.write('{'); + while(iter.hasNext()){ + if (first) { + first = false; + } + else { + out.write(','); + } + Map.Entry entry = (Map.Entry) iter.next(); + out.write('\"'); + out.write(escape(String.valueOf(entry.getKey()))); + out.write('\"'); + out.write(':'); + JSONValue.writeJSONString(entry.getValue(), out); + } + out.write('}'); + } + + public void writeJSONString(Writer out) throws IOException{ + writeJSONString(this, out); + } + + /** + * Convert a map to JSON text. The result is a JSON object. + * If this map is also a JSONAware, JSONAware specific behaviours will be omitted at this top level. + * + * @see com.alibaba.nacos.client.logger.json.JSONValue#toJSONString(Object) + * + * @param map + * @return JSON text, or "null" if map is null. + */ + public static String toJSONString(Map map){ + final StringWriter writer = new StringWriter(); + + try { + writeJSONString(map, writer); + return writer.toString(); + } catch (IOException e) { + // This should never happen with a StringWriter + throw new RuntimeException(e); + } + } + + public String toJSONString(){ + return toJSONString(this); + } + + public String toString(){ + return toJSONString(); + } + + public static String toString(String key,Object value){ + StringBuffer sb = new StringBuffer(); + sb.append('\"'); + if(key == null) { + sb.append("null"); + } + else { + JSONValue.escape(key, sb); + } + sb.append('\"').append(':'); + + sb.append(JSONValue.toJSONString(value)); + + return sb.toString(); + } + + /** + * Escape quotes, \, /, \r, \n, \b, \f, \t and other control characters (U+0000 through U+001F). + * It's the same as JSONValue.escape() only for compatibility here. + * + * @see com.alibaba.nacos.client.logger.json.JSONValue#escape(String) + * + * @param s + * @return + */ + public static String escape(String s){ + return JSONValue.escape(s); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/json/JSONStreamAware.java b/client/src/main/java/com/alibaba/nacos/client/logger/json/JSONStreamAware.java new file mode 100644 index 00000000000..97c75790265 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/json/JSONStreamAware.java @@ -0,0 +1,38 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.json; + +import java.io.IOException; +import java.io.Writer; + +/** + * Beans that support customized output of JSON text to a writer shall implement + * this interface. + * + * @author FangYidong + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public interface JSONStreamAware { + /** + * write JSON string to out. + * + * @param out + * out writer + * @throws IOException + * Exception + */ + void writeJSONString(Writer out) throws IOException; +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/json/JSONValue.java b/client/src/main/java/com/alibaba/nacos/client/logger/json/JSONValue.java new file mode 100644 index 00000000000..47e15586aaf --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/json/JSONValue.java @@ -0,0 +1,343 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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. + */ +/* + * $Id: JSONValue.java,v 1.1 2006/04/15 14:37:04 platform Exp $ + * Created on 2006-4-15 + */ +package com.alibaba.nacos.client.logger.json; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; +import java.util.Collection; +import java.util.Map; + +import com.alibaba.nacos.client.logger.json.parser.JSONParser; +import com.alibaba.nacos.client.logger.json.parser.ParseException; + + + +/** + * @author FangYidong + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public class JSONValue { + /** + * Parse JSON text into java object from the input source. + * Please use parseWithException() if you don't want to ignore the exception. + * + * @see com.alibaba.nacos.client.logger.jsonparser.JSONParser#parse(Reader) + * @see #parseWithException(Reader) + * + * @param in + * @return Instance of the following: + * com.alibaba.nacos.client.logger.jsonJSONObject, + * com.alibaba.nacos.client.logger.jsonJSONArray, + * java.lang.String, + * java.lang.Number, + * java.lang.Boolean, + * null + * + * @deprecated this method may throw an {@code Error} instead of returning + * {@code null}; please use {@link JSONValue#parseWithException(Reader)} + * instead + */ + public static Object parse(Reader in){ + try{ + JSONParser parser=new JSONParser(); + return parser.parse(in); + } + catch(Exception e){ + return null; + } + } + + /** + * Parse JSON text into java object from the given string. + * Please use parseWithException() if you don't want to ignore the exception. + * + * @see com.alibaba.nacos.client.logger.jsonparser.JSONParser#parse(Reader) + * @see #parseWithException(Reader) + * + * @param s + * @return Instance of the following: + * com.alibaba.nacos.client.logger.jsonJSONObject, + * com.alibaba.nacos.client.logger.jsonJSONArray, + * java.lang.String, + * java.lang.Number, + * java.lang.Boolean, + * null + * + * @deprecated this method may throw an {@code Error} instead of returning + * {@code null}; please use {@link JSONValue#parseWithException(String)} + * instead + */ + public static Object parse(String s){ + StringReader in=new StringReader(s); + return parse(in); + } + + /** + * Parse JSON text into java object from the input source. + * + * @see com.alibaba.nacos.client.logger.jsonparser.JSONParser + * + * @param in + * @return Instance of the following: + * com.alibaba.nacos.client.logger.jsonJSONObject, + * com.alibaba.nacos.client.logger.jsonJSONArray, + * java.lang.String, + * java.lang.Number, + * java.lang.Boolean, + * null + * + * @throws IOException + * @throws ParseException + */ + public static Object parseWithException(Reader in) throws IOException, ParseException { + JSONParser parser=new JSONParser(); + return parser.parse(in); + } + + public static Object parseWithException(String s) throws ParseException{ + JSONParser parser=new JSONParser(); + return parser.parse(s); + } + + /** + * Encode an object into JSON text and write it to out. + *

+ * If this object is a Map or a List, and it's also a JSONStreamAware or a JSONAware, JSONStreamAware or JSONAware will be considered firstly. + *

+ * DO NOT call this method from writeJSONString(Writer) of a class that implements both JSONStreamAware and (Map or List) with + * "this" as the first parameter, use JSONObject.writeJSONString(Map, Writer) or JSONArray.writeJSONString(List, Writer) instead. + * + * @see com.alibaba.nacos.client.logger.jsonJSONObject#writeJSONString(Map, Writer) + * @see com.alibaba.nacos.client.logger.jsonJSONArray#writeJSONString(List, Writer) + * + * @param value + * @param writer + */ + public static void writeJSONString(Object value, Writer out) throws IOException { + if(value == null){ + out.write("null"); + return; + } + + if(value instanceof String){ + out.write('\"'); + out.write(escape((String)value)); + out.write('\"'); + return; + } + + if(value instanceof Double){ + if(((Double)value).isInfinite() || ((Double)value).isNaN()) { + out.write("null"); + } + else { + out.write(value.toString()); + } + return; + } + + if(value instanceof Float){ + if(((Float)value).isInfinite() || ((Float)value).isNaN()) { + out.write("null"); + } + else { + out.write(value.toString()); + } + return; + } + + if(value instanceof Number){ + out.write(value.toString()); + return; + } + + if(value instanceof Boolean){ + out.write(value.toString()); + return; + } + + if((value instanceof JSONStreamAware)){ + ((JSONStreamAware)value).writeJSONString(out); + return; + } + + if((value instanceof JSONAware)){ + out.write(((JSONAware)value).toJSONString()); + return; + } + + if(value instanceof Map){ + JSONObject.writeJSONString((Map)value, out); + return; + } + + if(value instanceof Collection){ + JSONArray.writeJSONString((Collection)value, out); + return; + } + + if(value instanceof byte[]){ + JSONArray.writeJSONString((byte[])value, out); + return; + } + + if(value instanceof short[]){ + JSONArray.writeJSONString((short[])value, out); + return; + } + + if(value instanceof int[]){ + JSONArray.writeJSONString((int[])value, out); + return; + } + + if(value instanceof long[]){ + JSONArray.writeJSONString((long[])value, out); + return; + } + + if(value instanceof float[]){ + JSONArray.writeJSONString((float[])value, out); + return; + } + + if(value instanceof double[]){ + JSONArray.writeJSONString((double[])value, out); + return; + } + + if(value instanceof boolean[]){ + JSONArray.writeJSONString((boolean[])value, out); + return; + } + + if(value instanceof char[]){ + JSONArray.writeJSONString((char[])value, out); + return; + } + + if(value instanceof Object[]){ + JSONArray.writeJSONString((Object[])value, out); + return; + } + + out.write(value.toString()); + } + + /** + * Convert an object to JSON text. + *

+ * If this object is a Map or a List, and it's also a JSONAware, JSONAware will be considered firstly. + *

+ * DO NOT call this method from toJSONString() of a class that implements both JSONAware and Map or List with + * "this" as the parameter, use JSONObject.toJSONString(Map) or JSONArray.toJSONString(List) instead. + * + * @see com.alibaba.nacos.client.logger.json.JSONObject#toJSONString(Map) + * + * @param value + * @return JSON text, or "null" if value is null or it's an NaN or an INF number. + */ + public static String toJSONString(Object value){ + final StringWriter writer = new StringWriter(); + + try{ + writeJSONString(value, writer); + return writer.toString(); + } catch(IOException e){ + // This should never happen for a StringWriter + throw new RuntimeException(e); + } + } + + /** + * Escape quotes, \, /, \r, \n, \b, \f, \t and other control characters (U+0000 through U+001F). + * @param s + * @return + */ + public static String escape(String s){ + if(s==null) { + return null; + } + StringBuffer sb = new StringBuffer(); + escape(s, sb); + return sb.toString(); + } + + /** + * @param s - Must not be null. + * @param sb + */ + static void escape(String s, StringBuffer sb) { + final int len = s.length(); + for(int i=0;i= '\u0000' && ch <= '\u001F') || (ch >= '\u007F' && ch <= '\u009F') + || (ch >= '\u2000' && ch <= '\u20FF'); + } + + private static int FOUR = 4; + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/json/parser/ContainerFactory.java b/client/src/main/java/com/alibaba/nacos/client/logger/json/parser/ContainerFactory.java new file mode 100644 index 00000000000..0185fe1667f --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/json/parser/ContainerFactory.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.json.parser; + +import java.util.List; +import java.util.Map; + +/** + * Container factory for creating containers for JSON object and JSON array. + * + * @see com.alibaba.nacos.client.logger.json.parser.JSONParser#parse(java.io.Reader, ContainerFactory) + * + * @author FangYidong + */ +public interface ContainerFactory { + /** + * create json container + * @return A Map instance to store JSON object, or null if you want to use com.alibaba.nacos.client.logger.jsonJSONObject. + */ + Map createObjectContainer(); + + /** + * create array json container + * @return A List instance to store JSON array, or null if you want to use com.alibaba.nacos.client.logger.jsonJSONArray. + */ + List creatArrayContainer(); +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/json/parser/ContentHandler.java b/client/src/main/java/com/alibaba/nacos/client/logger/json/parser/ContentHandler.java new file mode 100644 index 00000000000..09e9decad93 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/json/parser/ContentHandler.java @@ -0,0 +1,129 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.json.parser; + +import java.io.IOException; + +/** + * A simplified and stoppable SAX-like content handler for stream processing of JSON text. + * + * @see org.xml.sax.ContentHandler + * @see com.alibaba.nacos.client.logger.json.parser.JSONParser#parse(java.io.Reader, ContentHandler, boolean) + * + * @author FangYidong + */ +public interface ContentHandler { + /** + * Receive notification of the beginning of JSON processing. + * The parser will invoke this method only once. + * + * @throws ParseException + * - JSONParser will stop and throw the same exception to the caller when receiving this exception. + * @throws IOException + */ + void startJSON() throws ParseException, IOException; + + /** + * Receive notification of the end of JSON processing. + * + * @throws ParseException + * @throws IOException + */ + void endJSON() throws ParseException, IOException; + + /** + * Receive notification of the beginning of a JSON object. + * + * @return false if the handler wants to stop parsing after return. + * @throws ParseException + * - JSONParser will stop and throw the same exception to the caller when receiving this exception. + * @throws IOException + * @see #endJSON + */ + boolean startObject() throws ParseException, IOException; + + /** + * Receive notification of the end of a JSON object. + * + * @return false if the handler wants to stop parsing after return. + * @throws ParseException + * @throws IOException + * @see #startObject + */ + boolean endObject() throws ParseException, IOException; + + /** + * Receive notification of the beginning of a JSON object entry. + * + * @param key - Key of a JSON object entry. + * + * @return false if the handler wants to stop parsing after return. + * @throws ParseException + * @throws IOException + * @see #endObjectEntry + */ + boolean startObjectEntry(String key) throws ParseException, IOException; + + /** + * Receive notification of the end of the value of previous object entry. + * + * @return false if the handler wants to stop parsing after return. + * @throws ParseException + * @throws IOException + * @see #startObjectEntry + */ + boolean endObjectEntry() throws ParseException, IOException; + + /** + * Receive notification of the beginning of a JSON array. + * + * @return false if the handler wants to stop parsing after return. + * @throws ParseException + * @throws IOException + * @see #endArray + */ + boolean startArray() throws ParseException, IOException; + + /** + * Receive notification of the end of a JSON array. + * + * @return false if the handler wants to stop parsing after return. + * @throws ParseException + * @throws IOException + * @see #startArray + */ + boolean endArray() throws ParseException, IOException; + + /** + * Receive notification of the JSON primitive values: + * java.lang.String, + * java.lang.Number, + * java.lang.Boolean + * null + * + * @param value - Instance of the following: + * java.lang.String, + * java.lang.Number, + * java.lang.Boolean + * null + * + * @return false if the handler wants to stop parsing after return. + * @throws ParseException + * @throws IOException + */ + boolean primitive(Object value) throws ParseException, IOException; + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/json/parser/JSONParser.java b/client/src/main/java/com/alibaba/nacos/client/logger/json/parser/JSONParser.java new file mode 100644 index 00000000000..a7bcee0d608 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/json/parser/JSONParser.java @@ -0,0 +1,574 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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. + */ +/* + * $Id: JSONParser.java,v 1.1 2006/04/15 14:10:48 platform Exp $ + * Created on 2006-4-15 + */ +package com.alibaba.nacos.client.logger.json.parser; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import com.alibaba.nacos.client.logger.json.JSONArray; +import com.alibaba.nacos.client.logger.json.JSONObject; + + +/** + * Parser for JSON text. Please note that JSONParser is NOT thread-safe. + * + * @author FangYidong + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public class JSONParser { + public static final int S_INIT=0; + public static final int S_IN_FINISHED_VALUE=1; + public static final int S_IN_OBJECT=2; + public static final int S_IN_ARRAY=3; + public static final int S_PASSED_PAIR_KEY=4; + public static final int S_IN_PAIR_VALUE=5; + public static final int S_END=6; + public static final int S_IN_ERROR=-1; + + private LinkedList handlerStatusStack; + private Yylex lexer = new Yylex((Reader)null); + private Yytoken token = null; + private int status = S_INIT; + + private int peekStatus(LinkedList statusStack){ + if(statusStack.size()==0) { + return -1; + } + Integer status=(Integer)statusStack.getFirst(); + return status.intValue(); + } + + /** + * Reset the parser to the initial state without resetting the underlying reader. + * + */ + public void reset(){ + token = null; + status = S_INIT; + handlerStatusStack = null; + } + + /** + * Reset the parser to the initial state with a new character reader. + * + * @param in - The new character reader. + * @throws IOException + * @throws ParseException + */ + public void reset(Reader in){ + lexer.yyreset(in); + reset(); + } + + /** + * @return The position of the beginning of the current token. + */ + public int getPosition(){ + return lexer.getPosition(); + } + + public Object parse(String s) throws ParseException{ + return parse(s, (ContainerFactory)null); + } + + public Object parse(String s, ContainerFactory containerFactory) throws ParseException{ + StringReader in=new StringReader(s); + try{ + return parse(in, containerFactory); + } + catch(IOException ie){ + /* + * Actually it will never happen. + */ + throw new ParseException(-1, ParseException.ERROR_UNEXPECTED_EXCEPTION, ie); + } + } + + public Object parse(Reader in) throws IOException, ParseException{ + return parse(in, (ContainerFactory)null); + } + + /** + * Parse JSON text into java object from the input source. + * + * @param in + * @param containerFactory - Use this factory to createyour own JSON object and JSON array containers. + * @return Instance of the following: + * com.alibaba.nacos.client.logger.jsonJSONObject, + * com.alibaba.nacos.client.logger.jsonJSONArray, + * java.lang.String, + * java.lang.Number, + * java.lang.Boolean, + * null + * + * @throws IOException + * @throws ParseException + */ + public Object parse(Reader in, ContainerFactory containerFactory) throws IOException, ParseException{ + reset(in); + LinkedList statusStack = new LinkedList(); + LinkedList valueStack = new LinkedList(); + + try{ + do{ + nextToken(); + switch(status){ + case S_INIT: + switch(token.type){ + case Yytoken.TYPE_VALUE: + status=S_IN_FINISHED_VALUE; + statusStack.addFirst(Integer.valueOf(status)); + valueStack.addFirst(token.value); + break; + case Yytoken.TYPE_LEFT_BRACE: + status=S_IN_OBJECT; + statusStack.addFirst(Integer.valueOf(status)); + valueStack.addFirst(createObjectContainer(containerFactory)); + break; + case Yytoken.TYPE_LEFT_SQUARE: + status=S_IN_ARRAY; + statusStack.addFirst(Integer.valueOf(status)); + valueStack.addFirst(createArrayContainer(containerFactory)); + break; + default: + status=S_IN_ERROR; + }//inner switch + break; + + case S_IN_FINISHED_VALUE: + if(token.type==Yytoken.TYPE_EOF) { + return valueStack.removeFirst(); + } + else { + throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); + } + case S_IN_OBJECT: + switch(token.type){ + case Yytoken.TYPE_COMMA: + break; + case Yytoken.TYPE_VALUE: + if(token.value instanceof String){ + String key=(String)token.value; + valueStack.addFirst(key); + status=S_PASSED_PAIR_KEY; + statusStack.addFirst(Integer.valueOf(status)); + } + else{ + status=S_IN_ERROR; + } + break; + case Yytoken.TYPE_RIGHT_BRACE: + if(valueStack.size()>1){ + statusStack.removeFirst(); + valueStack.removeFirst(); + status=peekStatus(statusStack); + } + else{ + status=S_IN_FINISHED_VALUE; + } + break; + default: + status=S_IN_ERROR; + break; + }//inner switch + break; + + case S_PASSED_PAIR_KEY: + switch(token.type){ + case Yytoken.TYPE_COLON: + break; + case Yytoken.TYPE_VALUE: + statusStack.removeFirst(); + String key=(String)valueStack.removeFirst(); + Map parent=(Map)valueStack.getFirst(); + parent.put(key,token.value); + status=peekStatus(statusStack); + break; + case Yytoken.TYPE_LEFT_SQUARE: + statusStack.removeFirst(); + key=(String)valueStack.removeFirst(); + parent=(Map)valueStack.getFirst(); + List newArray=createArrayContainer(containerFactory); + parent.put(key,newArray); + status=S_IN_ARRAY; + statusStack.addFirst(Integer.valueOf(status)); + valueStack.addFirst(newArray); + break; + case Yytoken.TYPE_LEFT_BRACE: + statusStack.removeFirst(); + key=(String)valueStack.removeFirst(); + parent=(Map)valueStack.getFirst(); + Map newObject=createObjectContainer(containerFactory); + parent.put(key,newObject); + status=S_IN_OBJECT; + statusStack.addFirst(Integer.valueOf(status)); + valueStack.addFirst(newObject); + break; + default: + status=S_IN_ERROR; + } + break; + + case S_IN_ARRAY: + switch(token.type){ + case Yytoken.TYPE_COMMA: + break; + case Yytoken.TYPE_VALUE: + List val=(List)valueStack.getFirst(); + val.add(token.value); + break; + case Yytoken.TYPE_RIGHT_SQUARE: + if(valueStack.size()>1){ + statusStack.removeFirst(); + valueStack.removeFirst(); + status=peekStatus(statusStack); + } + else{ + status=S_IN_FINISHED_VALUE; + } + break; + case Yytoken.TYPE_LEFT_BRACE: + val=(List)valueStack.getFirst(); + Map newObject=createObjectContainer(containerFactory); + val.add(newObject); + status=S_IN_OBJECT; + statusStack.addFirst(Integer.valueOf(status)); + valueStack.addFirst(newObject); + break; + case Yytoken.TYPE_LEFT_SQUARE: + val=(List)valueStack.getFirst(); + List newArray=createArrayContainer(containerFactory); + val.add(newArray); + status=S_IN_ARRAY; + statusStack.addFirst(Integer.valueOf(status)); + valueStack.addFirst(newArray); + break; + default: + status=S_IN_ERROR; + }//inner switch + break; + case S_IN_ERROR: + throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); + default: + break; + }//switch + if(status==S_IN_ERROR){ + throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); + } + }while(token.type!=Yytoken.TYPE_EOF); + } + catch(IOException ie){ + throw ie; + } + + throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); + } + + private void nextToken() throws ParseException, IOException{ + token = lexer.yylex(); + if(token == null) { + token = new Yytoken(Yytoken.TYPE_EOF, null); + } + } + + private Map createObjectContainer(ContainerFactory containerFactory){ + if(containerFactory == null) { + return new JSONObject(); + } + Map m = containerFactory.createObjectContainer(); + + if(m == null) { + return new JSONObject(); + } + return m; + } + + private List createArrayContainer(ContainerFactory containerFactory){ + if(containerFactory == null) { + return new JSONArray(); + } + List l = containerFactory.creatArrayContainer(); + + if(l == null) { + return new JSONArray(); + } + return l; + } + + public void parse(String s, ContentHandler contentHandler) throws ParseException{ + parse(s, contentHandler, false); + } + + public void parse(String s, ContentHandler contentHandler, boolean isResume) throws ParseException{ + StringReader in=new StringReader(s); + try{ + parse(in, contentHandler, isResume); + } + catch(IOException ie){ + /* + * Actually it will never happen. + */ + throw new ParseException(-1, ParseException.ERROR_UNEXPECTED_EXCEPTION, ie); + } + } + + public void parse(Reader in, ContentHandler contentHandler) throws IOException, ParseException{ + parse(in, contentHandler, false); + } + + /** + * Stream processing of JSON text. + * + * @see ContentHandler + * + * @param in + * @param contentHandler + * @param isResume - Indicates if it continues previous parsing operation. + * If set to true, resume parsing the old stream, and parameter 'in' will be ignored. + * If this method is called for the first time in this instance, isResume will be ignored. + * + * @throws IOException + * @throws ParseException + */ + public void parse(Reader in, ContentHandler contentHandler, boolean isResume) throws IOException, ParseException{ + if(!isResume){ + reset(in); + handlerStatusStack = new LinkedList(); + } + else{ + if(handlerStatusStack == null){ + isResume = false; + reset(in); + handlerStatusStack = new LinkedList(); + } + } + + LinkedList statusStack = handlerStatusStack; + + try{ + do{ + switch(status){ + case S_INIT: + contentHandler.startJSON(); + nextToken(); + switch(token.type){ + case Yytoken.TYPE_VALUE: + status=S_IN_FINISHED_VALUE; + statusStack.addFirst(Integer.valueOf(status)); + if(!contentHandler.primitive(token.value)) { + return; + } + break; + case Yytoken.TYPE_LEFT_BRACE: + status=S_IN_OBJECT; + statusStack.addFirst(Integer.valueOf(status)); + if(!contentHandler.startObject()) { + return; + } + break; + case Yytoken.TYPE_LEFT_SQUARE: + status=S_IN_ARRAY; + statusStack.addFirst(Integer.valueOf(status)); + if(!contentHandler.startArray()) { + return; + } + break; + default: + status=S_IN_ERROR; + }//inner switch + break; + + case S_IN_FINISHED_VALUE: + nextToken(); + if(token.type==Yytoken.TYPE_EOF){ + contentHandler.endJSON(); + status = S_END; + return; + } + else{ + status = S_IN_ERROR; + throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); + } + + case S_IN_OBJECT: + nextToken(); + switch(token.type){ + case Yytoken.TYPE_COMMA: + break; + case Yytoken.TYPE_VALUE: + if(token.value instanceof String){ + String key=(String)token.value; + status=S_PASSED_PAIR_KEY; + statusStack.addFirst(Integer.valueOf(status)); + if(!contentHandler.startObjectEntry(key)) { + return; + } + } + else{ + status=S_IN_ERROR; + } + break; + case Yytoken.TYPE_RIGHT_BRACE: + if(statusStack.size()>1){ + statusStack.removeFirst(); + status=peekStatus(statusStack); + } + else{ + status=S_IN_FINISHED_VALUE; + } + if(!contentHandler.endObject()) { + return; + } + break; + default: + status=S_IN_ERROR; + break; + }//inner switch + break; + + case S_PASSED_PAIR_KEY: + nextToken(); + switch(token.type){ + case Yytoken.TYPE_COLON: + break; + case Yytoken.TYPE_VALUE: + statusStack.removeFirst(); + status=peekStatus(statusStack); + if(!contentHandler.primitive(token.value)) { + return; + } + if(!contentHandler.endObjectEntry()) { + return; + } + break; + case Yytoken.TYPE_LEFT_SQUARE: + statusStack.removeFirst(); + statusStack.addFirst(Integer.valueOf(S_IN_PAIR_VALUE)); + status=S_IN_ARRAY; + statusStack.addFirst(Integer.valueOf(status)); + if(!contentHandler.startArray()) { + return; + } + break; + case Yytoken.TYPE_LEFT_BRACE: + statusStack.removeFirst(); + statusStack.addFirst(Integer.valueOf(S_IN_PAIR_VALUE)); + status=S_IN_OBJECT; + statusStack.addFirst(Integer.valueOf(status)); + if(!contentHandler.startObject()) { + return; + } + break; + default: + status=S_IN_ERROR; + } + break; + + case S_IN_PAIR_VALUE: + /* + * S_IN_PAIR_VALUE is just a marker to indicate the end of an object entry, it doesn't proccess any token, + * therefore delay consuming token until next round. + */ + statusStack.removeFirst(); + status = peekStatus(statusStack); + if(!contentHandler.endObjectEntry()) { + return; + } + break; + + case S_IN_ARRAY: + nextToken(); + switch(token.type){ + case Yytoken.TYPE_COMMA: + break; + case Yytoken.TYPE_VALUE: + if(!contentHandler.primitive(token.value)) { + return; + } + break; + case Yytoken.TYPE_RIGHT_SQUARE: + if(statusStack.size()>1){ + statusStack.removeFirst(); + status=peekStatus(statusStack); + } + else{ + status=S_IN_FINISHED_VALUE; + } + if(!contentHandler.endArray()) { + return; + } + break; + case Yytoken.TYPE_LEFT_BRACE: + status=S_IN_OBJECT; + statusStack.addFirst(Integer.valueOf(status)); + if(!contentHandler.startObject()) { + return; + } + break; + case Yytoken.TYPE_LEFT_SQUARE: + status=S_IN_ARRAY; + statusStack.addFirst(Integer.valueOf(status)); + if(!contentHandler.startArray()) { + return; + } + break; + default: + status=S_IN_ERROR; + }//inner switch + break; + + case S_END: + return; + + case S_IN_ERROR: + throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); + default: + break; + }//switch + if(status==S_IN_ERROR){ + throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); + } + }while(token.type!=Yytoken.TYPE_EOF); + } + catch(IOException ie){ + status = S_IN_ERROR; + throw ie; + } + catch(ParseException pe){ + status = S_IN_ERROR; + throw pe; + } + catch(RuntimeException re){ + status = S_IN_ERROR; + throw re; + } + catch(Error e){ + status = S_IN_ERROR; + throw e; + } + + status = S_IN_ERROR; + throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/json/parser/ParseException.java b/client/src/main/java/com/alibaba/nacos/client/logger/json/parser/ParseException.java new file mode 100644 index 00000000000..bd8d02c2acc --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/json/parser/ParseException.java @@ -0,0 +1,105 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.json.parser; + +/** + * ParseException explains why and where the error occurs in source JSON text. + * + * @author FangYidong + * + */ +public class ParseException extends Exception { + private static final long serialVersionUID = -7880698968187728547L; + + public static final int ERROR_UNEXPECTED_CHAR = 0; + public static final int ERROR_UNEXPECTED_TOKEN = 1; + public static final int ERROR_UNEXPECTED_EXCEPTION = 2; + + private int errorType; + private Object unexpectedObject; + private int position; + + public ParseException(int errorType){ + this(-1, errorType, null); + } + + public ParseException(int errorType, Object unexpectedObject){ + this(-1, errorType, unexpectedObject); + } + + public ParseException(int position, int errorType, Object unexpectedObject){ + this.position = position; + this.errorType = errorType; + this.unexpectedObject = unexpectedObject; + } + + public int getErrorType() { + return errorType; + } + + public void setErrorType(int errorType) { + this.errorType = errorType; + } + + /** + * @see com.alibaba.nacos.client.logger.json.parser.JSONParser#getPosition() + * + * @return The character position (starting with 0) of the input where the error occurs. + */ + public int getPosition() { + return position; + } + + public void setPosition(int position) { + this.position = position; + } + + /** + * @see com.alibaba.nacos.client.logger.json.parser.Yytoken + * + * @return One of the following base on the value of errorType: + * ERROR_UNEXPECTED_CHAR java.lang.Character + * ERROR_UNEXPECTED_TOKEN com.alibaba.nacos.client.logger.jsonparser.Yytoken + * ERROR_UNEXPECTED_EXCEPTION java.lang.Exception + */ + public Object getUnexpectedObject() { + return unexpectedObject; + } + + public void setUnexpectedObject(Object unexpectedObject) { + this.unexpectedObject = unexpectedObject; + } + + public String getMessage() { + StringBuffer sb = new StringBuffer(); + + switch(errorType){ + case ERROR_UNEXPECTED_CHAR: + sb.append("Unexpected character (").append(unexpectedObject).append(") at position ").append(position).append("."); + break; + case ERROR_UNEXPECTED_TOKEN: + sb.append("Unexpected token ").append(unexpectedObject).append(" at position ").append(position).append("."); + break; + case ERROR_UNEXPECTED_EXCEPTION: + sb.append("Unexpected exception at position ").append(position).append(": ").append(unexpectedObject); + break; + default: + sb.append("Unkown error at position ").append(position).append("."); + break; + } + return sb.toString(); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/json/parser/Yylex.java b/client/src/main/java/com/alibaba/nacos/client/logger/json/parser/Yylex.java new file mode 100644 index 00000000000..8c094d97ad1 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/json/parser/Yylex.java @@ -0,0 +1,665 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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. + */ +/* The following code was generated by JFlex 1.4.2 */ + +package com.alibaba.nacos.client.logger.json.parser; + +import java.io.UnsupportedEncodingException; + +import com.alibaba.nacos.client.config.common.Constants; + +/** + * Yylex + * @author Nacos + * + */ +class Yylex { + + /** This character denotes the end of file */ + public static final int YYEOF = -1; + + /** initial size of the lookahead buffer */ + private static final int ZZ_BUFFERSIZE = 16384; + + /** lexical states */ + public static final int YYINITIAL = 0; + public static final int STRING_BEGIN = 2; + + /** + * ZZ_LEXSTATE[l] is the state in the DFA for the lexical state l + * ZZ_LEXSTATE[l+1] is the state in the DFA for the lexical state l + * at the beginning of a line + * l is of the form l = 2*k, k a non negative integer + */ + private static final int ZZ_LEXSTATE[] = { 0, 0, 1, 1 }; + + /** + * Translates characters to character classes + */ + private static final String ZZ_CMAP_PACKED = + "\11\0\1\7\1\7\2\0\1\7\22\0\1\7\1\0\1\11\10\0" + "\1\6\1\31\1\2\1\4\1\12\12\3\1\32\6\0\4\1\1\5" + + "\1\1\24\0\1\27\1\10\1\30\3\0\1\22\1\13\2\1\1\21" + "\1\14\5\0\1\23\1\0\1\15\3\0\1\16\1\24\1\17\1\20" + + "\5\0\1\25\1\0\1\26\uff82\0"; + + /** + * Translates characters to character classes + */ + private static final char[] ZZ_CMAP = zzUnpackCMap(ZZ_CMAP_PACKED); + + /** + * Translates DFA states to action switch labels. + */ + private static final int[] ZZ_ACTION = zzUnpackAction(); + + private static final String ZZ_ACTION_PACKED_0 = + "\2\0\2\1\1\2\1\3\1\4\3\1\1\5\1\6" + "\1\7\1\10\1\11\1\12\1\13\1\14\1\15\5\0" + + "\1\14\1\16\1\17\1\20\1\21\1\22\1\23\1\24" + "\1\0\1\25\1\0\1\25\4\0\1\26\1\27\2\0" + "\1\30"; + + private static int[] zzUnpackAction() { + int[] result = new int[45]; + int offset = 0; + zzUnpackAction(ZZ_ACTION_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackAction(String packed, int offset, int[] result) { + int i = 0; + int j = offset; + int l = packed.length(); + while (i < l) { + int count = packed.charAt(i++); + int value = packed.charAt(i++); + do result[j++] = value; while (--count > 0); + } + return j; + } + + /** + * Translates a state to a row index in the transition table + */ + private static final int[] ZZ_ROWMAP = zzUnpackRowMap(); + + private static final String ZZ_ROWMAP_PACKED_0 = + "\0\0\0\33\0\66\0\121\0\154\0\207\0\66\0\242" + "\0\275\0\330\0\66\0\66\0\66\0\66\0\66\0\66" + + "\0\363\0\u010e\0\66\0\u0129\0\u0144\0\u015f\0\u017a\0\u0195" + "\0\66\0\66\0\66\0\66\0\66\0\66\0\66\0\66" + + "\0\u01b0\0\u01cb\0\u01e6\0\u01e6\0\u0201\0\u021c\0\u0237\0\u0252" + "\0\66\0\66\0\u026d\0\u0288\0\66"; + + private static int[] zzUnpackRowMap() { + int[] result = new int[45]; + int offset = 0; + zzUnpackRowMap(ZZ_ROWMAP_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackRowMap(String packed, int offset, int[] result) { + int i = 0; + int j = offset; + int l = packed.length(); + while (i < l) { + int high = packed.charAt(i++) << 16; + result[j++] = high | packed.charAt(i++); + } + return j; + } + + /** + * The transition table of the DFA + */ + private static final int ZZ_TRANS[] = { 2, 2, 3, 4, 2, 2, 2, 5, 2, 6, 2, 2, 7, 8, 2, 9, 2, 2, 2, 2, 2, 10, 11, 12, + 13, 14, 15, 16, 16, 16, 16, 16, 16, 16, 16, 17, 18, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 4, 19, 20, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 20, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 21, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 22, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 23, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 16, 16, 16, 16, 16, 16, 16, 16, -1, + -1, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, -1, + -1, -1, -1, -1, -1, -1, -1, 24, 25, 26, 27, 28, 29, 30, 31, 32, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 33, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 34, 35, + -1, -1, 34, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 36, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 37, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 38, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 39, -1, 39, -1, 39, -1, -1, -1, -1, + -1, 39, 39, -1, -1, -1, -1, 39, 39, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 33, -1, 20, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 20, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 38, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 40, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 41, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 42, -1, 42, -1, 42, -1, -1, -1, -1, -1, 42, 42, -1, -1, -1, -1, 42, 42, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 43, -1, 43, -1, 43, -1, -1, -1, -1, -1, + 43, 43, -1, -1, -1, -1, 43, 43, -1, -1, -1, -1, -1, -1, -1, -1, -1, 44, -1, + 44, -1, 44, -1, -1, -1, -1, -1, 44, 44, -1, -1, -1, -1, 44, 44, -1, -1, -1, + -1, -1, -1, -1, -1, }; + + /** + * error codes + */ + private static final int ZZ_UNKNOWN_ERROR = 0; + private static final int ZZ_NO_MATCH = 1; + private static final int ZZ_PUSHBACK_2BIG = 2; + private static final int NIGTY = 90; + /** + * error messages for the codes above + */ + private static final String[] ZZ_ERROR_MSG = { "Unkown internal scanner error", "Error: could not match input", + "Error: pushback value was too large" }; + + /** + * ZZ_ATTRIBUTE[aState] contains the attributes of state aState + */ + private static final int[] ZZ_ATTRIBUTE = zzUnpackAttribute(); + + private static final String ZZ_ATTRIBUTE_PACKED_0 = + "\2\0\1\11\3\1\1\11\3\1\6\11\2\1\1\11" + "\5\0\10\11\1\0\1\1\1\0\1\1\4\0\2\11" + "\2\0\1\11"; + + private static int[] zzUnpackAttribute() { + int[] result = new int[45]; + int offset = 0; + zzUnpackAttribute(ZZ_ATTRIBUTE_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackAttribute(String packed, int offset, int[] result) { + int i = 0; + int j = offset; + int l = packed.length(); + while (i < l) { + int count = packed.charAt(i++); + int value = packed.charAt(i++); + do result[j++] = value; while (--count > 0); + } + return j; + } + + /** the input device */ + private java.io.Reader zzReader; + + /** the current state of the DFA */ + private int zzState; + + /** the current lexical state */ + private int zzLexicalState = YYINITIAL; + + /** + * this buffer contains the current text to be matched and is + * the source of the yytext() string + */ + private char zzBuffer[] = new char[ZZ_BUFFERSIZE]; + + /** the textposition at the last accepting state */ + private int zzMarkedPos; + + /** the current text position in the buffer */ + private int zzCurrentPos; + + /** startRead marks the beginning of the yytext() string in the buffer */ + private int zzStartRead; + + /** + * endRead marks the last character in the buffer, that has been read + * from input + */ + private int zzEndRead; + + /** the number of characters up to the start of the matched text */ + private int yychar; + + /** zzAtEOF == true <=> the scanner is at the EOF */ + private boolean zzAtEOF; + + /** + * user code + */ + private StringBuffer sb = new StringBuffer(); + + int getPosition() { + return yychar; + } + + /** + * Creates a new scanner + * There is also a java.io.InputStream version of this constructor. + * + * @param in the java.io.Reader to read input from. + */ + Yylex(java.io.Reader in) { + this.zzReader = in; + } + + /** + * Creates a new scanner. + * There is also java.io.Reader version of this constructor. + * + * @param in the java.io.Inputstream to read input from. + * @throws UnsupportedEncodingException + */ + Yylex(java.io.InputStream in) throws UnsupportedEncodingException { + this(new java.io.InputStreamReader(in, Constants.ENCODE)); + } + + /** + * Unpacks the compressed character translation table. + * + * @param packed the packed character translation table + * @return the unpacked character translation table + */ + private static char[] zzUnpackCMap(String packed) { + char[] map = new char[0x10000]; + int i = 0; + int j = 0; + while (i < NIGTY) { + int count = packed.charAt(i++); + char value = packed.charAt(i++); + do map[j++] = value; while (--count > 0); + } + return map; + } + + /** + * Refills the input buffer. + * + * @return false, iff there was new input. + * @throws java.io.IOException if any I/O-Error occurs + */ + private boolean zzRefill() throws java.io.IOException { + + /* first: make room (if you can) */ + if (zzStartRead > 0) { + System.arraycopy(zzBuffer, zzStartRead, zzBuffer, 0, zzEndRead - zzStartRead); + + /* translate stored positions */ + zzEndRead -= zzStartRead; + zzCurrentPos -= zzStartRead; + zzMarkedPos -= zzStartRead; + zzStartRead = 0; + } + + /* is the buffer big enough? */ + if (zzCurrentPos >= zzBuffer.length) { + /* if not: blow it up */ + char newBuffer[] = new char[zzCurrentPos * 2]; + System.arraycopy(zzBuffer, 0, newBuffer, 0, zzBuffer.length); + zzBuffer = newBuffer; + } + + /* finally: fill the buffer with new input */ + int numRead = zzReader.read(zzBuffer, zzEndRead, zzBuffer.length - zzEndRead); + + if (numRead > 0) { + zzEndRead += numRead; + return false; + } + // unlikely but not impossible: read 0 characters, but not at end of stream + if (numRead == 0) { + int c = zzReader.read(); + if (c == -1) { + return true; + } else { + zzBuffer[zzEndRead++] = (char) c; + return false; + } + } + + // numRead < 0 + return true; + } + + /** + * Closes the input stream. + */ + public final void yyclose() throws java.io.IOException { + zzAtEOF = true; + zzEndRead = zzStartRead; + + if (zzReader != null) { + zzReader.close(); + } + } + + /** + * Resets the scanner to read from a new input stream. + * Does not close the old reader. + * All internal variables are reset, the old input stream + * cannot be reused (internal buffer is discarded and lost). + * Lexical state is set to ZZ_INITIAL. + * + * @param reader the new input stream + */ + public final void yyreset(java.io.Reader reader) { + zzReader = reader; + zzAtEOF = false; + zzEndRead = zzStartRead = 0; + zzCurrentPos = zzMarkedPos = 0; + zzLexicalState = YYINITIAL; + } + + /** + * Returns the current lexical state. + */ + public final int yystate() { + return zzLexicalState; + } + + /** + * Enters a new lexical state + * + * @param newState the new lexical state + */ + public final void yybegin(int newState) { + zzLexicalState = newState; + } + + /** + * Returns the text matched by the current regular expression. + */ + public final String yytext() { + return new String(zzBuffer, zzStartRead, zzMarkedPos - zzStartRead); + } + + /** + * Returns the character at position pos from the + * matched text. + * It is equivalent to yytext().charAt(pos), but faster + * + * @param pos the position of the character to fetch. + * A value from 0 to yylength()-1. + * @return the character at position pos + */ + public final char yycharat(int pos) { + return zzBuffer[zzStartRead + pos]; + } + + /** + * Returns the length of the matched text region. + */ + public final int yylength() { + return zzMarkedPos - zzStartRead; + } + + /** + * Reports an error that occured while scanning. + * In a wellformed scanner (no or only correct usage of + * yypushback(int) and a match-all fallback rule) this method + * will only be called with things that "Can't Possibly Happen". + * If this method is called, something is seriously wrong + * (e.g. a JFlex bug producing a faulty scanner etc.). + * Usual syntax/scanner level error handling should be done + * in error fallback rules. + * + * @param errorCode the code of the errormessage to display + */ + private void zzScanError(int errorCode) { + String message; + try { + message = ZZ_ERROR_MSG[errorCode]; + } catch (ArrayIndexOutOfBoundsException e) { + message = ZZ_ERROR_MSG[ZZ_UNKNOWN_ERROR]; + } + + throw new Error(message); + } + + /** + * Pushes the specified amount of characters back into the input stream. + * They will be read again by then next call of the scanning method + * + * @param number the number of characters to be read again. + * This number must not be greater than yylength()! + */ + public void yypushback(int number) { + if (number > yylength()) { + zzScanError(ZZ_PUSHBACK_2BIG); + } + + zzMarkedPos -= number; + } + + /** + * Resumes scanning until the next regular expression is matched, + * the end of input is encountered or an I/O-Error occurs. + * + * @return the next token + * @throws java.io.IOException if any I/O-Error occurs + */ + @SuppressWarnings("PMD.SwitchStatementRule") + public Yytoken yylex() throws java.io.IOException, ParseException { + int zzInput; + int zzAction; + + // cached fields: + int zzCurrentPosL; + int zzMarkedPosL; + int zzEndReadL = zzEndRead; + char[] zzBufferL = zzBuffer; + char[] zzCMapL = ZZ_CMAP; + + int[] zzTransL = ZZ_TRANS; + int[] zzRowMapL = ZZ_ROWMAP; + int[] zzAttrL = ZZ_ATTRIBUTE; + + while (true) { + zzMarkedPosL = zzMarkedPos; + + yychar += zzMarkedPosL - zzStartRead; + + zzAction = -1; + + zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL; + + zzState = ZZ_LEXSTATE[zzLexicalState]; + + zzForAction: + { + while (true) { + + if (zzCurrentPosL < zzEndReadL) { + zzInput = zzBufferL[zzCurrentPosL++]; + } + else if (zzAtEOF) { + zzInput = YYEOF; + break zzForAction; + } else { + // store back cached positions + zzCurrentPos = zzCurrentPosL; + zzMarkedPos = zzMarkedPosL; + boolean eof = zzRefill(); + // get translated positions and possibly new buffer + zzCurrentPosL = zzCurrentPos; + zzMarkedPosL = zzMarkedPos; + zzBufferL = zzBuffer; + zzEndReadL = zzEndRead; + if (eof) { + zzInput = YYEOF; + break zzForAction; + } else { + zzInput = zzBufferL[zzCurrentPosL++]; + } + } + int zzNext = zzTransL[zzRowMapL[zzState] + zzCMapL[zzInput]]; + if (zzNext == -1) { + break zzForAction; + } + zzState = zzNext; + + int zzAttributes = zzAttrL[zzState]; + if ((zzAttributes & 1) == 1) { + zzAction = zzState; + zzMarkedPosL = zzCurrentPosL; + if ((zzAttributes & 8) == 8) { + break zzForAction; + } + } + + } + } + + // store back cached position + zzMarkedPos = zzMarkedPosL; + + switch (zzAction < 0 ? zzAction : ZZ_ACTION[zzAction]) { + case 11: { + sb.append(yytext()); + } + case 25: + break; + case 4: { + sb = null; + sb = new StringBuffer(); + yybegin(STRING_BEGIN); + } + case 26: + break; + case 16: { + sb.append('\b'); + } + case 27: + break; + case 6: { + return new Yytoken(Yytoken.TYPE_RIGHT_BRACE, null); + } + case 28: + break; + case 23: { + Boolean val = Boolean.valueOf(yytext()); + return new Yytoken(Yytoken.TYPE_VALUE, val); + } + case 29: + break; + case 22: { + return new Yytoken(Yytoken.TYPE_VALUE, null); + } + case 30: + break; + case 13: { + yybegin(YYINITIAL); + return new Yytoken(Yytoken.TYPE_VALUE, sb.toString()); + } + case 31: + break; + case 12: { + sb.append('\\'); + } + case 32: + break; + case 21: { + Double val = Double.valueOf(yytext()); + return new Yytoken(Yytoken.TYPE_VALUE, val); + } + case 33: + break; + case 1: { + throw new ParseException(yychar, ParseException.ERROR_UNEXPECTED_CHAR, Character.valueOf(yycharat(0))); + } + case 34: + break; + case 8: { + return new Yytoken(Yytoken.TYPE_RIGHT_SQUARE, null); + } + case 35: + break; + case 19: { + sb.append('\r'); + } + case 36: + break; + case 15: { + sb.append('/'); + } + case 37: + break; + case 10: { + return new Yytoken(Yytoken.TYPE_COLON, null); + } + case 38: + break; + case 14: { + sb.append('"'); + } + case 39: + break; + case 5: { + return new Yytoken(Yytoken.TYPE_LEFT_BRACE, null); + } + case 40: + break; + case 17: { + sb.append('\f'); + } + case 41: + break; + case 24: { + try { + int ch = Integer.parseInt(yytext().substring(2), 16); + sb.append((char) ch); + } catch (Exception e) { + throw new ParseException(yychar, ParseException.ERROR_UNEXPECTED_EXCEPTION, e); + } + } + case 42: + break; + case 20: { + sb.append('\t'); + } + case 43: + break; + case 7: { + return new Yytoken(Yytoken.TYPE_LEFT_SQUARE, null); + } + case 44: + break; + case 2: { + Long val = Long.valueOf(yytext()); + return new Yytoken(Yytoken.TYPE_VALUE, val); + } + case 45: + break; + case 18: { + sb.append('\n'); + } + case 46: + break; + case 9: { + return new Yytoken(Yytoken.TYPE_COMMA, null); + } + case 47: + break; + case 3: { + } + case 48: + break; + default: + if (zzInput == YYEOF && zzStartRead == zzCurrentPos) { + zzAtEOF = true; + return null; + } else { + zzScanError(ZZ_NO_MATCH); + } + } + } + } + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/json/parser/Yytoken.java b/client/src/main/java/com/alibaba/nacos/client/logger/json/parser/Yytoken.java new file mode 100644 index 00000000000..f07146f7ee9 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/json/parser/Yytoken.java @@ -0,0 +1,81 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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. + */ +/* + * $Id: Yytoken.java,v 1.1 2006/04/15 14:10:48 platform Exp $ + * Created on 2006-4-15 + */ +package com.alibaba.nacos.client.logger.json.parser; + +/** + * @author FangYidong + */ +public class Yytoken { + /** + * JSON primitive value: string,number,boolean,null + */ + public static final int TYPE_VALUE=0; + public static final int TYPE_LEFT_BRACE=1; + public static final int TYPE_RIGHT_BRACE=2; + public static final int TYPE_LEFT_SQUARE=3; + public static final int TYPE_RIGHT_SQUARE=4; + public static final int TYPE_COMMA=5; + public static final int TYPE_COLON=6; + /** + * end of file + */ + public static final int TYPE_EOF=-1; + + public int type=0; + public Object value=null; + + public Yytoken(int type,Object value){ + this.type=type; + this.value=value; + } + + public String toString(){ + StringBuffer sb = new StringBuffer(); + switch(type){ + case TYPE_VALUE: + sb.append("VALUE(").append(value).append(")"); + break; + case TYPE_LEFT_BRACE: + sb.append("LEFT BRACE({)"); + break; + case TYPE_RIGHT_BRACE: + sb.append("RIGHT BRACE(})"); + break; + case TYPE_LEFT_SQUARE: + sb.append("LEFT SQUARE([)"); + break; + case TYPE_RIGHT_SQUARE: + sb.append("RIGHT SQUARE(])"); + break; + case TYPE_COMMA: + sb.append("COMMA(,)"); + break; + case TYPE_COLON: + sb.append("COLON(:)"); + break; + case TYPE_EOF: + sb.append("END OF FILE"); + break; + default: + break; + } + return sb.toString(); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/log4j/Log4jLogger.java b/client/src/main/java/com/alibaba/nacos/client/logger/log4j/Log4jLogger.java new file mode 100644 index 00000000000..04389b494d2 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/log4j/Log4jLogger.java @@ -0,0 +1,146 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.log4j; + +import org.apache.log4j.Level; + +import com.alibaba.nacos.client.logger.Logger; +import com.alibaba.nacos.client.logger.option.Log4jActivateOption; +import com.alibaba.nacos.client.logger.support.LoggerHelper; +import com.alibaba.nacos.client.logger.support.LoggerSupport; +import com.alibaba.nacos.client.logger.util.MessageUtil; + + +/** + * Log4jLogger + * @author Nacos + * + */ +public class Log4jLogger extends LoggerSupport implements Logger { + + private org.apache.log4j.Logger delegate; + + public Log4jLogger(org.apache.log4j.Logger delegate) { + super(delegate); + + if (delegate == null) { + throw new IllegalArgumentException("delegate Logger is null"); + } + this.delegate = delegate; + + this.activateOption = new Log4jActivateOption(delegate); + } + + @Override + public void debug(String context, String message) { + if (isDebugEnabled()) { + message = LoggerHelper.getResourceBundleString(getProductName(), message); + delegate.debug(MessageUtil.getMessage(context, message)); + } + } + + @Override + public void debug(String context, String format, Object... args) { + if (isDebugEnabled()) { + format = LoggerHelper.getResourceBundleString(getProductName(), format); + delegate.debug(MessageUtil.getMessage(context, MessageUtil.formatMessage(format, args))); + } + } + + @Override + public void info(String context, String message) { + if (isInfoEnabled()) { + message = LoggerHelper.getResourceBundleString(getProductName(), message); + delegate.info(MessageUtil.getMessage(context, message)); + } + } + + @Override + public void info(String context, String format, Object... args) { + if (isInfoEnabled()) { + format = LoggerHelper.getResourceBundleString(getProductName(), format); + delegate.info(MessageUtil.getMessage(context, MessageUtil.formatMessage(format, args))); + } + } + + @Override + public void warn(String message, Throwable t) { + if (isWarnEnabled()) { + message = LoggerHelper.getResourceBundleString(getProductName(), message); + delegate.warn(MessageUtil.getMessage(null, message), t); + } + } + + @Override + public void warn(String context, String message) { + if (isWarnEnabled()) { + message = LoggerHelper.getResourceBundleString(getProductName(), message); + delegate.warn(MessageUtil.getMessage(context, message)); + } + } + + @Override + public void warn(String context, String format, Object... args) { + if (isWarnEnabled()) { + format = LoggerHelper.getResourceBundleString(getProductName(), format); + delegate.warn(MessageUtil.getMessage(context, MessageUtil.formatMessage(format, args))); + } + } + + @Override + public void error(String context, String errorCode, String message) { + if (isErrorEnabled()) { + message = LoggerHelper.getResourceBundleString(getProductName(), message); + delegate.error(MessageUtil.getMessage(context, errorCode, message)); + } + } + + @Override + public void error(String context, String errorCode, String message, Throwable t) { + if (isErrorEnabled()) { + message = LoggerHelper.getResourceBundleString(getProductName(), message); + delegate.error(MessageUtil.getMessage(context, errorCode, message), t); + } + } + + @Override + public void error(String context, String errorCode, String format, Object... args) { + if (isErrorEnabled()) { + format = LoggerHelper.getResourceBundleString(getProductName(), format); + delegate.error(MessageUtil.getMessage(context, errorCode, MessageUtil.formatMessage(format, args))); + } + } + + @Override + public boolean isDebugEnabled() { + return delegate.isDebugEnabled(); + } + + @Override + public boolean isInfoEnabled() { + return delegate.isInfoEnabled(); + } + + @Override + public boolean isWarnEnabled() { + return delegate.isEnabledFor(Level.WARN); + } + + @Override + public boolean isErrorEnabled() { + return delegate.isEnabledFor(Level.ERROR); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/log4j/Log4jLoggerFactory.java b/client/src/main/java/com/alibaba/nacos/client/logger/log4j/Log4jLoggerFactory.java new file mode 100644 index 00000000000..86ca0aa9f48 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/log4j/Log4jLoggerFactory.java @@ -0,0 +1,53 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.log4j; + +import org.apache.log4j.LogManager; + +import com.alibaba.nacos.client.logger.Logger; +import com.alibaba.nacos.client.logger.nop.NopLogger; +import com.alibaba.nacos.client.logger.support.ILoggerFactory; +import com.alibaba.nacos.client.logger.support.LogLog; + +/** + * Log4jLogger Factory + * @author Nacos + * + */ +public class Log4jLoggerFactory implements ILoggerFactory { + + public Log4jLoggerFactory() throws ClassNotFoundException { + Class.forName("org.apache.log4j.Level"); + } + + public Logger getLogger(Class clazz) { + try { + return new Log4jLogger(LogManager.getLogger(clazz)); + } catch (Throwable t) { + LogLog.error("Failed to get Log4jLogger", t); + return new NopLogger(); + } + } + + public Logger getLogger(String name) { + try { + return new Log4jLogger(LogManager.getLogger(name)); + } catch (Throwable t) { + LogLog.error("Failed to get Log4jLogger", t); + return new NopLogger(); + } + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/log4j2/Log4j2Logger.java b/client/src/main/java/com/alibaba/nacos/client/logger/log4j2/Log4j2Logger.java new file mode 100644 index 00000000000..2333d06fcf2 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/log4j2/Log4j2Logger.java @@ -0,0 +1,144 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.log4j2; + +import com.alibaba.nacos.client.logger.Logger; +import com.alibaba.nacos.client.logger.option.Log4j2ActivateOption; +import com.alibaba.nacos.client.logger.support.LoggerHelper; +import com.alibaba.nacos.client.logger.support.LoggerSupport; +import com.alibaba.nacos.client.logger.util.MessageUtil; + + +/** + * Log4j2Logger + * @author Nacos + * + */ +public class Log4j2Logger extends LoggerSupport implements Logger { + + private org.apache.logging.log4j.Logger delegate; + + public Log4j2Logger(org.apache.logging.log4j.Logger delegate) { + super(delegate); + + if (delegate == null) { + throw new IllegalArgumentException("delegate Logger is null"); + } + this.delegate = delegate; + + this.activateOption = new Log4j2ActivateOption(delegate); + } + + @Override + public void debug(String context, String message) { + if (isDebugEnabled()) { + message = LoggerHelper.getResourceBundleString(getProductName(), message); + delegate.debug(MessageUtil.getMessage(context, message)); + } + } + + @Override + public void debug(String context, String format, Object... args) { + if (isDebugEnabled()) { + format = LoggerHelper.getResourceBundleString(getProductName(), format); + delegate.debug(MessageUtil.getMessage(context, MessageUtil.formatMessage(format, args))); + } + } + + @Override + public void info(String context, String message) { + if (isInfoEnabled()) { + message = LoggerHelper.getResourceBundleString(getProductName(), message); + delegate.info(MessageUtil.getMessage(context, message)); + } + } + + @Override + public void info(String context, String format, Object... args) { + if (isInfoEnabled()) { + format = LoggerHelper.getResourceBundleString(getProductName(), format); + delegate.info(MessageUtil.getMessage(context, MessageUtil.formatMessage(format, args))); + } + } + + @Override + public void warn(String message, Throwable t) { + if (isWarnEnabled()) { + message = LoggerHelper.getResourceBundleString(getProductName(), message); + delegate.warn(MessageUtil.getMessage(null, message), t); + } + } + + @Override + public void warn(String context, String message) { + if (isWarnEnabled()) { + message = LoggerHelper.getResourceBundleString(getProductName(), message); + delegate.warn(MessageUtil.getMessage(context, message)); + } + } + + @Override + public void warn(String context, String format, Object... args) { + if (isWarnEnabled()) { + format = LoggerHelper.getResourceBundleString(getProductName(), format); + delegate.warn(MessageUtil.getMessage(context, MessageUtil.formatMessage(format, args))); + } + } + + @Override + public void error(String context, String errorCode, String message) { + if (isErrorEnabled()) { + message = LoggerHelper.getResourceBundleString(getProductName(), message); + delegate.error(MessageUtil.getMessage(context, errorCode, message)); + } + } + + @Override + public void error(String context, String errorCode, String message, Throwable t) { + if (isErrorEnabled()) { + message = LoggerHelper.getResourceBundleString(getProductName(), message); + delegate.error(MessageUtil.getMessage(context, errorCode, message), t); + } + } + + @Override + public void error(String context, String errorCode, String format, Object... args) { + if (isErrorEnabled()) { + format = LoggerHelper.getResourceBundleString(getProductName(), format); + delegate.error(MessageUtil.getMessage(context, errorCode, MessageUtil.formatMessage(format, args))); + } + } + + @Override + public boolean isDebugEnabled() { + return delegate.isDebugEnabled(); + } + + @Override + public boolean isInfoEnabled() { + return delegate.isInfoEnabled(); + } + + @Override + public boolean isWarnEnabled() { + return delegate.isWarnEnabled(); + } + + @Override + public boolean isErrorEnabled() { + return delegate.isErrorEnabled(); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/log4j2/Log4j2LoggerFactory.java b/client/src/main/java/com/alibaba/nacos/client/logger/log4j2/Log4j2LoggerFactory.java new file mode 100644 index 00000000000..11863cc3a64 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/log4j2/Log4j2LoggerFactory.java @@ -0,0 +1,55 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.log4j2; + +import org.apache.logging.log4j.LogManager; + +import com.alibaba.nacos.client.logger.Logger; +import com.alibaba.nacos.client.logger.nop.NopLogger; +import com.alibaba.nacos.client.logger.support.ILoggerFactory; +import com.alibaba.nacos.client.logger.support.LogLog; + +/** + * Log4j2Logger Factory + * @author Nacos + * + */ +public class Log4j2LoggerFactory implements ILoggerFactory { + + public Log4j2LoggerFactory() throws ClassNotFoundException { + Class.forName("org.apache.logging.log4j.core.Logger"); + } + + @Override + public Logger getLogger(Class clazz) { + try { + return new Log4j2Logger(LogManager.getLogger(clazz)); + } catch (Throwable t) { + LogLog.error("Failed to get Log4j2Logger", t); + return new NopLogger(); + } + } + + @Override + public Logger getLogger(String name) { + try { + return new Log4j2Logger(LogManager.getLogger(name)); + } catch (Throwable t) { + LogLog.error("Failed to get Log4j2Logger", t); + return new NopLogger(); + } + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/nop/NopLogger.java b/client/src/main/java/com/alibaba/nacos/client/logger/nop/NopLogger.java new file mode 100644 index 00000000000..48d12f69466 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/nop/NopLogger.java @@ -0,0 +1,99 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.nop; + +import com.alibaba.nacos.client.logger.Logger; +import com.alibaba.nacos.client.logger.support.LoggerSupport; +/** + * NopLogger + * @author Nacos + * + */ +public class NopLogger extends LoggerSupport implements Logger { + + public NopLogger(){ + super(null); + } + + @Override + public void debug(String context, String message) { + + } + + @Override + public void debug(String context, String format, Object... args) { + + } + + @Override + public void info(String context, String message) { + + } + + @Override + public void info(String context, String format, Object... args) { + + } + + public void warn(String message, Throwable t) { + + } + + @Override + public void warn(String context, String message) { + + } + + @Override + public void warn(String context, String format, Object... args) { + + } + + @Override + public void error(String context, String errorCode, String message) { + + } + + @Override + public void error(String context, String errorCode, String message, Throwable t) { + + } + + @Override + public void error(String context, String errorCode, String format, Object... args) { + + } + + @Override + public boolean isDebugEnabled() { + return false; + } + + @Override + public boolean isInfoEnabled() { + return false; + } + + @Override + public boolean isWarnEnabled() { + return false; + } + + @Override + public boolean isErrorEnabled() { + return false; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/nop/NopLoggerFactory.java b/client/src/main/java/com/alibaba/nacos/client/logger/nop/NopLoggerFactory.java new file mode 100644 index 00000000000..9bd6a9979cb --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/nop/NopLoggerFactory.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.nop; + +import com.alibaba.nacos.client.logger.Logger; +import com.alibaba.nacos.client.logger.support.ILoggerFactory; +/** + * NopLogger Factory + * @author Nacos + * + */ +public class NopLoggerFactory implements ILoggerFactory { + + @Override + public Logger getLogger(Class clazz) { + return new NopLogger(); + } + + @Override + public Logger getLogger(String name) { + return new NopLogger(); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/option/AbstractActiveOption.java b/client/src/main/java/com/alibaba/nacos/client/logger/option/AbstractActiveOption.java new file mode 100644 index 00000000000..209054e6816 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/option/AbstractActiveOption.java @@ -0,0 +1,73 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.option; + +import org.apache.log4j.AsyncAppender; + +import com.alibaba.nacos.client.logger.Level; +import com.alibaba.nacos.client.logger.support.LogLog; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; + +/** + * AbstractActiveOption + * @author Nacos + * + */ +public abstract class AbstractActiveOption implements ActivateOption { + + protected String productName; + protected Level level; + + @Override + public String getProductName() { + return productName; + } + + @Override + public Level getLevel() { + return level; + } + + protected void setProductName(String productName) { + if (this.productName == null && productName != null) { + this.productName = productName; + } + } + + public static void invokeMethod(Object object, List args) { + if (args != null && object != null) { + for (Object[] arg : args) { + if (arg != null && arg.length == 3) { + try { + Method m = object.getClass().getMethod((String) arg[0], (Class[]) arg[1]); + m.invoke(object, arg[2]); + } catch (NoSuchMethodException e) { + LogLog.info("Can't find method for " + object.getClass() + " " + arg[0] + " " + arg[2]); + } catch (IllegalAccessException e) { + LogLog.info("Can't invoke method for " + object.getClass() + " " + arg[0] + " " + arg[2]); + } catch (InvocationTargetException e) { + LogLog.info("Can't invoke method for " + object.getClass() + " " + arg[0] + " " + arg[2]); + } catch (Throwable t) { + LogLog.info("Can't invoke method for " + object.getClass() + " " + arg[0] + " " + arg[2]); + } + } + } + } + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/option/ActivateOption.java b/client/src/main/java/com/alibaba/nacos/client/logger/option/ActivateOption.java new file mode 100644 index 00000000000..6c882682962 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/option/ActivateOption.java @@ -0,0 +1,195 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.option; + +import java.util.List; +import java.util.Map; + +import com.alibaba.nacos.client.logger.Level; +import com.alibaba.nacos.client.logger.Logger; + +/** + *

+ * 激活Logger的选项,包括:
+ * Appender/Layout
+ * Level
+ * Additivity
+ * Aysnc
+ * 请参考具体的实现逻辑
+ * 
+ * + * @author zhuyong 2014年3月20日 上午10:20:51 + */ +public interface ActivateOption { + + /** + * 设置ConsoleAppender,生产环境慎用 + * + * @param target System.out or System.err + * @param encoding 编码 + */ + void activateConsoleAppender(String target, String encoding); + + /** + * 设置FileAppender,日志按天回滚 + * + * @param productName 中间件产品名,如hsf, tddl + * @param file 日志文件名,如hsf.log,支持子目录,如client/hsf.log + * @param encoding 编码 + */ + void activateAppender(String productName, String file, String encoding); + + /** + * 设置AsyncAppender,内嵌DailyRollingFileAppender,日志按天回滚,参考 {@link ActivateOption#activateAsync(int, int)} + * + * @param productName 中间件产品名,如hsf, tddl + * @param file 日志文件名,如hsf.log,支持子目录,如client/hsf.log + * @param encoding 编码 + */ + @Deprecated + void activateAsyncAppender(String productName, String file, String encoding); + + /** + * 设置AsyncAppender,内嵌DailyRollingFileAppender,日志按天回滚,参考 {@link ActivateOption#activateAsync(int, int)} + * + * @param productName 中间件产品名,如hsf, tddl + * @param file 日志文件名,如hsf.log,支持子目录,如client/hsf.log + * @param encoding 编码 + * @param queueSize 等待队列大小 + * @param discardingThreshold discardingThreshold,该参数仅对logback实现有效,log4j和log4j2无效 + */ + @Deprecated + void activateAsyncAppender(String productName, String file, String encoding, int queueSize, + int discardingThreshold); + + /** + * 设置按天和文件大小回滚 + * + * @param productName 中间件产品名,如hsf, tddl + * @param file 日志文件名,如hsf.log,支持子目录,如client/hsf.log + * @param encoding 编码 + * @param size 文件大小,如300MB,支持KB,MB,GB,该参数对log4j实现不生效,log4j2和logback有效 + */ + void activateAppenderWithTimeAndSizeRolling(String productName, String file, String encoding, String size); + + /** + *
+     * 设置按日期格式和文件大小回滚
+     * 说明:Log4j 对日期格式不生效,只有按大小回滚,同时不支持备份文件,即达到文件大小直接截断,如果需要备份文件,请参考带 maxBackupIndex 参数的方法
+     * 
+ * + * @param productName 中间件产品名,如hsf, tddl + * @param file 日志文件名,如hsf.log,支持子目录,如client/hsf.log + * @param encoding 编码 + * @param size 文件大小,如300MB,支持KB,MB,GB + * @param datePattern 日期格式,如yyyy-MM-dd 或 yyyy-MM,请自行保证格式正确,该参数对log4j实现不生效,log4j2和logback有效 + */ + void activateAppenderWithTimeAndSizeRolling(String productName, String file, String encoding, String size, + String datePattern); + + /** + *
+     * 设置按日期格式、文件大小、最大备份文件数回滚
+     * 说明:
+     * 1、Log4j 对日期格式不生效,只有按大小、备份文件数回滚,备份文件数 maxBackupIndex 参数必须是 >= 0 的整数,为0时表示直接截断,不备份
+     * 2、备份日志格式说明:
+     *     Log4j:notify.log.1, notify.log.2,即备份文件以 .1 .2结尾,序号从1开始
+     *     Logback: notify.log.2014-09-19.0, notify.log.2014-09-19.1,即中间会带日期格式,同时序号从0开始
+     * 
+ * + * @param productName 中间件产品名,如hsf, tddl + * @param file 日志文件名,如hsf.log,支持子目录,如client/hsf.log + * @param encoding 编码 + * @param size 文件大小,如300MB,支持KB,MB,GB + * @param datePattern 日期格式,如yyyy-MM-dd 或 yyyy-MM,请自行保证格式正确,该参数对log4j实现不生效,log4j2和logback有效 + * @param maxBackupIndex 最大备份文件数,如10(对于 Logback,则是保留10天的文件,但是这10天内的文件则会按大小回滚) + */ + void activateAppenderWithTimeAndSizeRolling(String productName, String file, String encoding, String size, + String datePattern, int maxBackupIndex); + + /** + *
+     * 设置按文件大小、最大备份文件数回滚
+     * 说明:
+     * 1、Log4j 备份文件数 maxBackupIndex 参数必须是 >= 0 的整数,为0时表示直接截断,不备份
+     * 2、备份日志格式说明:
+     *     Log4j:notify.log.1, notify.log.2,即备份文件以 .1 .2结尾,序号从1开始
+     *     Logback: notify.log.1, notify.log.1
+     * 
+ * + * @param productName 中间件产品名,如hsf, tddl + * @param file 日志文件名,如hsf.log,支持子目录,如client/hsf.log + * @param encoding 编码 + * @param size 文件大小,如300MB,支持KB,MB,GB + * @param maxBackupIndex 最大备份文件数,如10 + */ + void activateAppenderWithSizeRolling(String productName, String file, String encoding, String size, + int maxBackupIndex); + + /** + * 将当前logger对象的appender设置为异步Appender + * 注意:此logger需要提前进行Appender的初始化 + * + * @param queueSize 等待队列大小 + * @param discardingThreshold discardingThreshold,该参数仅对logback实现有效,log4j和log4j2无效 + * @since 0.2.2 + */ + void activateAsync(int queueSize, int discardingThreshold); + + /** + * 将当前logger对象的appender设置为异步Appender + * 注意:此logger需要提前进行Appender的初始化 + * + * @param args AsyncAppender配置参数,请自行保证参数的正确性,要求每个Object[]有3个元素,第一个为set方法名,第二个为方法类型数组,第三个为对应的参数值,如 + * args.add(new Object[] { "setBufferSize", new Class[] { int.class }, queueSize }); + * @since 0.2.3 + */ + void activateAsync(List args); + + /** + * 使用logger对象的appender来初始化当前logger + * + * @param logger + */ + void activateAppender(Logger logger); + + /** + * 设置日志级别 + * + * @param level 日志级别 + * @see Level + */ + void setLevel(Level level); + + /** + * 获取日志级别 + * @return level + */ + Level getLevel(); + + /** + * 设置日志是否Attach到Parent + * + * @param additivity true or false + */ + void setAdditivity(boolean additivity); + + /** + * 获取所属的产品名 + * @return 所属的产品名 + */ + String getProductName(); +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/option/Log4j2ActivateOption.java b/client/src/main/java/com/alibaba/nacos/client/logger/option/Log4j2ActivateOption.java new file mode 100644 index 00000000000..0dbccc18846 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/option/Log4j2ActivateOption.java @@ -0,0 +1,274 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.option; + +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AsyncAppender; +import org.apache.logging.log4j.core.appender.ConsoleAppender; +import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.apache.logging.log4j.core.appender.rolling.*; +import org.apache.logging.log4j.core.async.ArrayBlockingQueueFactory; +import org.apache.logging.log4j.core.config.AppenderRef; +import org.apache.logging.log4j.core.config.Configuration; + +import com.alibaba.nacos.client.logger.Level; +import com.alibaba.nacos.client.logger.Logger; +import com.alibaba.nacos.client.logger.support.LoggerHelper; + +import java.io.File; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; + +/** + * @author zhuyong on 2017/4/13. + */ +public class Log4j2ActivateOption extends AbstractActiveOption { + + protected org.apache.logging.log4j.core.Logger logger; + protected Configuration configuration; + + public Log4j2ActivateOption(org.apache.logging.log4j.Logger logger) { + if (logger != null) { + if (logger instanceof org.apache.logging.log4j.core.Logger) { + this.logger = (org.apache.logging.log4j.core.Logger) logger; + + configuration = this.logger.getContext().getConfiguration(); + } else { + throw new RuntimeException("logger must instanceof org.apache.logging.log4j.core.Logger, " + logger.getClass().getName()); + } + } + } + + @Override + public void activateConsoleAppender(String target, String encoding) { + org.apache.logging.log4j.core.Layout layout = org.apache.logging.log4j.core.layout.PatternLayout.newBuilder(). + withConfiguration(configuration) + .withPattern(LoggerHelper.getPattern()) + .withCharset(Charset.forName(encoding)) + .build(); + org.apache.logging.log4j.core.appender.ConsoleAppender appender = ConsoleAppender.createAppender(layout, null, + ConsoleAppender.Target.valueOf(target.toUpperCase().replace(".", "_")), "LoggerApiConsoleAppender", false, false, true); + appender.start(); + removeAllAppenders(logger); + logger.addAppender(appender); + + setProductName(productName); + } + + @Override + public void activateAppender(String productName, String file, String encoding) { + org.apache.logging.log4j.core.appender.RollingFileAppender appender = RollingFileAppender.newBuilder() + .withName(productName + "." + file.replace(File.separatorChar, '.') + ".Appender") + .withFileName(LoggerHelper.getLogFileP(productName, file)) + .withAppend(true) + .withBufferedIo(true) + .setConfiguration(configuration) + .withFilePattern(LoggerHelper.getLogFile(productName, file) + ".%d{yyyy-MM-dd}") + .withLayout(buildLayout(encoding)) + .withCreateOnDemand(false) + .withPolicy(TimeBasedTriggeringPolicy.createPolicy("1", "true")) + .withStrategy(DefaultRolloverStrategy.createStrategy(null, null, "nomax", null, null, false, configuration)) + .build(); + + appender.start(); + removeAllAppenders(logger); + logger.addAppender(appender); + + setProductName(productName); + } + + @Override + public void activateAsyncAppender(String productName, String file, String encoding) { + activateAsyncAppender(productName, file, encoding, Integer.MIN_VALUE, Integer.MIN_VALUE); + } + + @Override + public void activateAsyncAppender(String productName, String file, String encoding, int queueSize, int discardingThreshold) { + activateAppender(productName, file, encoding); + activateAsync(queueSize, discardingThreshold); + } + + @Override + public void activateAppenderWithTimeAndSizeRolling(String productName, String file, String encoding, String size) { + activateAppenderWithTimeAndSizeRolling(productName, file, encoding, size, "yyyy-MM-dd"); + } + + @Override + public void activateAppenderWithTimeAndSizeRolling(String productName, String file, String encoding, String size, String datePattern) { + org.apache.logging.log4j.core.appender.RollingFileAppender appender = RollingFileAppender.newBuilder() + .withName(productName + "." + file.replace(File.separatorChar, '.') + ".Appender") + .withFileName(LoggerHelper.getLogFileP(productName, file)) + .withAppend(true) + .withBufferedIo(true) + .setConfiguration(configuration) + .withFilePattern(LoggerHelper.getLogFile(productName, file) + ".%d{" + datePattern + "}") + .withLayout(buildLayout(encoding)) + .withCreateOnDemand(false) + .withPolicy(CompositeTriggeringPolicy.createPolicy(TimeBasedTriggeringPolicy.createPolicy("1", "true"), SizeBasedTriggeringPolicy.createPolicy(size))) + .withStrategy(DefaultRolloverStrategy.createStrategy(null, null, "nomax", null, null, false, configuration)) + .build(); + + appender.start(); + removeAllAppenders(logger); + logger.addAppender(appender); + + setProductName(productName); + } + + @Override + public void activateAppenderWithTimeAndSizeRolling(String productName, String file, String encoding, String size, String datePattern, int maxBackupIndex) { + org.apache.logging.log4j.core.appender.RollingFileAppender appender = RollingFileAppender.newBuilder() + .withName(productName + "." + file.replace(File.separatorChar, '.') + ".Appender") + .withFileName(LoggerHelper.getLogFileP(productName, file)) + .withAppend(true) + .withBufferedIo(true) + .setConfiguration(configuration) + .withFilePattern(LoggerHelper.getLogFile(productName, file) + ".%d{" + datePattern + "}.%i") + .withLayout(buildLayout(encoding)) + .withCreateOnDemand(false) + .withPolicy(CompositeTriggeringPolicy.createPolicy(TimeBasedTriggeringPolicy.createPolicy("1", "true"), SizeBasedTriggeringPolicy.createPolicy(size))) + .withStrategy(DefaultRolloverStrategy.createStrategy(String.valueOf(maxBackupIndex), "1", "max", null, null, false, configuration)) + .build(); + + appender.start(); + removeAllAppenders(logger); + logger.addAppender(appender); + + setProductName(productName); + } + + @Override + public void activateAppenderWithSizeRolling(String productName, String file, String encoding, String size, int maxBackupIndex) { + org.apache.logging.log4j.core.appender.RollingFileAppender appender = RollingFileAppender.newBuilder() + .withName(productName + "." + file.replace(File.separatorChar, '.') + ".Appender") + .withFileName(LoggerHelper.getLogFileP(productName, file)) + .withAppend(true) + .withBufferedIo(true) + .setConfiguration(configuration) + .withFilePattern(LoggerHelper.getLogFile(productName, file) + ".%i") + .withLayout(buildLayout(encoding)) + .withCreateOnDemand(false) + .withPolicy(SizeBasedTriggeringPolicy.createPolicy(size)) + .withStrategy(DefaultRolloverStrategy.createStrategy(String.valueOf(maxBackupIndex), "1", "max", null, null, false, configuration)) + .build(); + + appender.start(); + removeAllAppenders(logger); + logger.addAppender(appender); + + setProductName(productName); + } + + @Override + public void activateAsync(int queueSize, int discardingThreshold) { + List args = new ArrayList(); + + if (queueSize != Integer.MIN_VALUE) { + args.add(new Object[] { "setBufferSize", new Class[] { int.class }, queueSize }); + } + activateAsync(args); + } + + @Override + public void activateAsync(List args) { + Map appenders = logger.getAppenders(); + if (appenders == null) { + throw new IllegalStateException("Activate async appender failed, no appender exist."); + } + + AppenderRef[] refs = new AppenderRef[appenders.size()]; + int i = 0; + for (Appender appender : appenders.values()) { + configuration.addAppender(appender); + refs[i++] = AppenderRef.createAppenderRef(appender.getName(), null, null); + } + + AsyncAppender.Builder builder = AsyncAppender.newBuilder() + .setName(productName + "." + logger.getName() + ".AsyncAppender") + .setConfiguration(configuration) + .setAppenderRefs(refs) + .setBlockingQueueFactory(ArrayBlockingQueueFactory.createFactory()); + + invokeMethod(builder, args); + + AsyncAppender asyncAppender = builder.build(); + asyncAppender.start(); + + removeAllAppenders(logger); + logger.addAppender(asyncAppender); + + setProductName(productName); + } + + @Override + public void activateAppender(Logger logger) { + if (!(logger.getDelegate() instanceof org.apache.logging.log4j.core.Logger)) { + throw new IllegalArgumentException("logger must be org.apache.logging.log4j.core.Logger, but it's " + + logger.getDelegate().getClass()); + } + + activateAppender(((org.apache.logging.log4j.core.Logger) logger.getDelegate())); + + setProductName(logger.getProductName()); + } + + protected void activateAppender(org.apache.logging.log4j.core.Logger logger) { + removeAllAppenders(this.logger); + + Map appenders = null; + if ((appenders = logger.getAppenders()) != null) { + for (Appender appender : appenders.values()) { + this.logger.addAppender(appender); + } + } + } + + @Override + public void setLevel(Level level) { + this.level = level; + + org.apache.logging.log4j.Level l = org.apache.logging.log4j.Level.toLevel(level.getName(), org.apache.logging.log4j.Level.ERROR); + logger.setLevel(l); + logger.getContext().getConfiguration().getLoggerConfig(this.logger.getName()).setLevel(l); + } + + @Override + public void setAdditivity(boolean additivity) { + logger.setAdditive(additivity); + } + + protected org.apache.logging.log4j.core.Layout buildLayout(String encoding) { + org.apache.logging.log4j.core.Layout layout = org.apache.logging.log4j.core.layout.PatternLayout.newBuilder(). + withConfiguration(configuration) + .withPattern(LoggerHelper.getPattern()) + .withCharset(Charset.forName(encoding)) + .build(); + return layout; + } + + protected void removeAllAppenders(org.apache.logging.log4j.core.Logger logger) { + Map appenders = logger.getAppenders(); + if (appenders != null) { + for (Appender appender : appenders.values()) { + logger.removeAppender(appender); + } + } + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/option/Log4jActivateOption.java b/client/src/main/java/com/alibaba/nacos/client/logger/option/Log4jActivateOption.java new file mode 100644 index 00000000000..ca3e3a6f9a9 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/option/Log4jActivateOption.java @@ -0,0 +1,212 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.option; + +import org.apache.log4j.*; + +import com.alibaba.nacos.client.logger.Logger; +import com.alibaba.nacos.client.logger.support.LoggerHelper; + +import java.io.File; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * ActivateOption的Log4j实现 + * + * @author zhuyong 2014年3月20日 上午10:24:36 + */ +public class Log4jActivateOption extends AbstractActiveOption { + + protected org.apache.log4j.Logger logger; + + public Log4jActivateOption(org.apache.log4j.Logger logger) { + this.logger = logger; + } + + @Override + public void activateConsoleAppender(String target, String encoding) { + org.apache.log4j.ConsoleAppender appender = new org.apache.log4j.ConsoleAppender(); + appender.setLayout(new PatternLayout(LoggerHelper.getPattern())); + appender.setTarget(target); + appender.setEncoding(encoding); + appender.activateOptions(); + + logger.removeAllAppenders(); + logger.addAppender(appender); + } + + @Override + public void activateAppender(String productName, String file, String encoding) { + org.apache.log4j.Appender appender = getLog4jDailyRollingFileAppender(productName, file, encoding); + logger.removeAllAppenders(); + logger.addAppender(appender); + + setProductName(productName); + } + + @Override + public void activateAsyncAppender(String productName, String file, String encoding) { + activateAsyncAppender(productName, file, encoding, Integer.MIN_VALUE, Integer.MIN_VALUE); + } + + @Override + public void activateAsyncAppender(String productName, String file, String encoding, int queueSize, + int discardingThreshold) { + activateAppender(productName, file, encoding); + activateAsync(queueSize, discardingThreshold); + } + + @Override + public void activateAppenderWithTimeAndSizeRolling(String productName, String file, String encoding, String size) { + activateAppender(productName, file, encoding); + } + + @Override + public void setLevel(com.alibaba.nacos.client.logger.Level level) { + this.level = level; + logger.setLevel(org.apache.log4j.Level.toLevel(level.getName())); + } + + @Override + public void setAdditivity(boolean additivity) { + logger.setAdditivity(additivity); + } + + protected org.apache.log4j.Appender getLog4jDailyRollingFileAppender(String productName, String file, + String encoding) { + DailyRollingFileAppender appender = new DailyRollingFileAppender(); + appender.setName(productName + "." + file.replace(File.separatorChar, '.') + ".Appender"); + appender.setLayout(new PatternLayout(LoggerHelper.getPattern(productName))); + appender.setAppend(true); + appender.setFile(LoggerHelper.getLogFileP(productName, file)); + appender.setEncoding(encoding); + appender.activateOptions(); + + return appender; + } + + @Override + public void activateAppender(Logger logger) { + if (!(logger.getDelegate() instanceof org.apache.log4j.Logger)) { + throw new IllegalArgumentException( + "logger must be org.apache.log4j.Logger, but it's " + logger.getDelegate().getClass()); + } + activateAppender((org.apache.log4j.Logger) logger.getDelegate()); + + setProductName(logger.getProductName()); + } + + protected void activateAppender(org.apache.log4j.Logger logger) { + this.logger.removeAllAppenders(); + + Enumeration enums = logger.getAllAppenders(); + while (enums != null && enums.hasMoreElements()) { + this.logger.addAppender((Appender) enums.nextElement()); + } + } + + @Override + public void activateAppenderWithTimeAndSizeRolling(String productName, String file, String encoding, String size, + String datePattern) { + Appender appender = getLog4jRollingFileAppender(productName, file, encoding, size, datePattern, -1); + + logger.removeAllAppenders(); + logger.addAppender(appender); + + setProductName(productName); + } + + @Override + public void activateAppenderWithTimeAndSizeRolling(String productName, String file, String encoding, String size, + String datePattern, int maxBackupIndex) { + Appender appender = getLog4jRollingFileAppender(productName, file, encoding, size, datePattern, maxBackupIndex); + logger.removeAllAppenders(); + logger.addAppender(appender); + + setProductName(productName); + } + + protected org.apache.log4j.Appender getLog4jRollingFileAppender(String productName, String file, String encoding, + String size, String datePattern, + int maxBackupIndex) { + RollingFileAppender appender = new RollingFileAppender(); + appender.setName(productName + "." + file.replace(File.separatorChar, '.') + ".Appender"); + appender.setLayout(new PatternLayout(LoggerHelper.getPattern(productName))); + appender.setAppend(true); + appender.setFile(LoggerHelper.getLogFileP(productName, file)); + appender.setEncoding(encoding); + appender.setMaxFileSize(size); + if (maxBackupIndex >= 0) { + // 等于0表示直接truck + appender.setMaxBackupIndex(maxBackupIndex); + } + appender.activateOptions(); + + return appender; + } + + @Override + public void activateAppenderWithSizeRolling(String productName, String file, String encoding, String size, + int maxBackupIndex) { + Appender appender = getLog4jRollingFileAppender(productName, file, encoding, size, null, maxBackupIndex); + logger.removeAllAppenders(); + logger.addAppender(appender); + + setProductName(productName); + } + + @Override + public void activateAsync(int queueSize, int discardingThreshold) { + // discardingThreshold is unused for log4j + List args = new ArrayList(); + + if (queueSize != Integer.MIN_VALUE) { + args.add(new Object[] { "setBufferSize", new Class[] { int.class }, queueSize }); + } + activateAsync(args); + } + + @Override + public void activateAsync(List args) { + AsyncAppender asyncAppender = new AsyncAppender(); + + invokeMethod(asyncAppender, args); + + asyncAppender.setName(productName + "." + logger.getName() + ".AsyncAppender"); + Enumeration appenders = logger.getAllAppenders(); + + if (appenders == null) { + throw new IllegalStateException("Activate async appender failed, no appender exist."); + } + + while (appenders.hasMoreElements()) { + asyncAppender.addAppender(appenders.nextElement()); + } + + appenders = logger.getAllAppenders(); + while (appenders.hasMoreElements()) { + logger.removeAppender(appenders.nextElement()); + } + + logger.addAppender(asyncAppender); + + setProductName(productName); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/option/Logback918ActivateOption.java b/client/src/main/java/com/alibaba/nacos/client/logger/option/Logback918ActivateOption.java new file mode 100644 index 00000000000..319f31a59db --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/option/Logback918ActivateOption.java @@ -0,0 +1,334 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.option; + +import ch.qos.logback.classic.AsyncAppender; +import ch.qos.logback.classic.PatternLayout; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import ch.qos.logback.core.rolling.*; +import ch.qos.logback.core.util.FileSize; + +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import com.alibaba.nacos.client.logger.Level; +import com.alibaba.nacos.client.logger.Logger; +import com.alibaba.nacos.client.logger.support.LogLog; +import com.alibaba.nacos.client.logger.support.LoggerHelper; + +/** + * logback 0.9.18版本及以前适用 + * + * @author zhuyong 2014年3月20日 上午11:16:26 + */ +@SuppressWarnings({ "rawtypes", "unchecked" }) +public class Logback918ActivateOption extends AbstractActiveOption { + + private ch.qos.logback.classic.Logger logger; + + public Logback918ActivateOption(Object logger) { + if (logger instanceof ch.qos.logback.classic.Logger) { + this.logger = (ch.qos.logback.classic.Logger) logger; + } else { + throw new IllegalArgumentException("logger must be instanceof ch.qos.logback.classic.Logger"); + } + } + + @Override + public void activateConsoleAppender(String target, String encoding) { + ch.qos.logback.core.ConsoleAppender appender = new ch.qos.logback.core.ConsoleAppender(); + appender.setContext(LogbackLoggerContextUtil.getLoggerContext()); + appender.setTarget(target); + PatternLayout layout = new PatternLayout(); + layout.setPattern(LoggerHelper.getPattern()); + layout.setContext(LogbackLoggerContextUtil.getLoggerContext()); + layout.start(); + appender.setLayout(layout); + appender.start(); + + logger.detachAndStopAllAppenders(); + logger.addAppender(appender); + } + + @Override + public void activateAppender(String productName, String file, String encoding) { + ch.qos.logback.core.Appender appender = getLogbackDailyRollingFileAppender(productName, file, encoding); + logger.detachAndStopAllAppenders(); + logger.addAppender(appender); + + setProductName(productName); + } + + @Override + public void activateAsyncAppender(String productName, String file, String encoding) { + activateAsyncAppender(productName, file, encoding, Integer.MIN_VALUE, Integer.MIN_VALUE); + } + + public void activateAsyncAppender(String productName, String file, String encoding, int queueSize, + int discardingThreshold) { + activateAppender(productName, file, encoding); + activateAsync(queueSize, discardingThreshold); + } + + @Override + public void setLevel(Level level) { + this.level = level; + logger.setLevel(ch.qos.logback.classic.Level.valueOf(level.getName())); + } + + @Override + public void setAdditivity(boolean additivity) { + logger.setAdditive(additivity); + } + + @Override + public void activateAppenderWithTimeAndSizeRolling(String productName, String file, String encoding, String size) { + ch.qos.logback.core.Appender appender = getLogbackDailyAndSizeRollingFileAppender(productName, file, encoding, + size); + logger.detachAndStopAllAppenders(); + logger.addAppender(appender); + + setProductName(productName); + } + + protected ch.qos.logback.core.Appender getLogbackDailyRollingFileAppender(String productName, String file, + String encoding) { + RollingFileAppender appender = new RollingFileAppender(); + appender.setContext(LogbackLoggerContextUtil.getLoggerContext()); + + appender.setName(productName + "." + file.replace(File.separatorChar, '.') + ".Appender"); + appender.setAppend(true); + appender.setFile(LoggerHelper.getLogFile(productName, file)); + + TimeBasedRollingPolicy rolling = new TimeBasedRollingPolicy(); + rolling.setParent(appender); + rolling.setFileNamePattern(LoggerHelper.getLogFile(productName, file) + ".%d{yyyy-MM-dd}"); + rolling.setContext(LogbackLoggerContextUtil.getLoggerContext()); + rolling.start(); + appender.setRollingPolicy(rolling); + + PatternLayout layout = new PatternLayout(); + layout.setPattern(LoggerHelper.getPattern(productName)); + layout.setContext(LogbackLoggerContextUtil.getLoggerContext()); + layout.start(); + appender.setLayout(layout); + + // 启动 + appender.start(); + + return appender; + } + + protected ch.qos.logback.core.Appender getLogbackDailyAndSizeRollingFileAppender(String productName, String file, + String encoding, String size) { + return getLogbackDailyAndSizeRollingFileAppender(productName, file, encoding, size, "yyyy-MM-dd", -1); + } + + @Override + public void activateAppender(Logger logger) { + if (!(logger.getDelegate() instanceof ch.qos.logback.classic.Logger)) { + throw new IllegalArgumentException( + "logger must be ch.qos.logback.classic.Logger, but it's " + logger.getDelegate().getClass()); + } + this.logger.detachAndStopAllAppenders(); + + Iterator> iter = ((ch.qos.logback.classic.Logger) logger.getDelegate()).iteratorForAppenders(); + while (iter.hasNext()) { + ch.qos.logback.core.Appender appender = iter.next(); + this.logger.addAppender(appender); + } + } + + @Override + public void activateAppenderWithTimeAndSizeRolling(String productName, String file, String encoding, String size, + String datePattern) { + ch.qos.logback.core.Appender appender = getLogbackDailyAndSizeRollingFileAppender(productName, file, encoding, + size, datePattern, -1); + logger.detachAndStopAllAppenders(); + logger.addAppender(appender); + + setProductName(productName); + } + + protected ch.qos.logback.core.Appender getLogbackDailyAndSizeRollingFileAppender(String productName, String file, + String encoding, String size, + String datePattern, + int maxBackupIndex) { + RollingFileAppender appender = new RollingFileAppender(); + appender.setContext(LogbackLoggerContextUtil.getLoggerContext()); + + appender.setName(productName + "." + file.replace(File.separatorChar, '.') + ".Appender"); + appender.setAppend(true); + appender.setFile(LoggerHelper.getLogFile(productName, file)); + + TimeBasedRollingPolicy rolling = new TimeBasedRollingPolicy(); + rolling.setParent(appender); + if (maxBackupIndex >= 0) { + rolling.setMaxHistory(maxBackupIndex); + } + rolling.setFileNamePattern(LoggerHelper.getLogFile(productName, file) + ".%d{" + datePattern + "}.%i"); + rolling.setContext(LogbackLoggerContextUtil.getLoggerContext()); + + SizeAndTimeBasedFNATP fnatp = new SizeAndTimeBasedFNATP(); + setMaxFileSize(fnatp, size); + fnatp.setTimeBasedRollingPolicy(rolling); + rolling.setTimeBasedFileNamingAndTriggeringPolicy(fnatp); + + rolling.start(); + appender.setRollingPolicy(rolling); + + PatternLayout layout = new PatternLayout(); + layout.setPattern(LoggerHelper.getPattern(productName)); + layout.setContext(LogbackLoggerContextUtil.getLoggerContext()); + layout.start(); + appender.setLayout(layout); + + // 启动 + appender.start(); + + return appender; + } + + @Override + public void activateAppenderWithTimeAndSizeRolling(String productName, String file, String encoding, String size, + String datePattern, int maxBackupIndex) { + ch.qos.logback.core.Appender appender = getLogbackDailyAndSizeRollingFileAppender(productName, file, encoding, + size, datePattern, + maxBackupIndex); + logger.detachAndStopAllAppenders(); + logger.addAppender(appender); + + setProductName(productName); + } + + @Override + public void activateAppenderWithSizeRolling(String productName, String file, String encoding, String size, + int maxBackupIndex) { + ch.qos.logback.core.Appender appender = getSizeRollingAppender(productName, file, encoding, size, + maxBackupIndex); + logger.detachAndStopAllAppenders(); + logger.addAppender(appender); + + setProductName(productName); + } + + @Override + public void activateAsync(int queueSize, int discardingThreshold) { + List args = new ArrayList(); + + if (queueSize != Integer.MIN_VALUE) { + args.add(new Object[] { "setQueueSize", new Class[] { int.class }, queueSize }); + } + + if (discardingThreshold != Integer.MIN_VALUE) { + args.add(new Object[] { "setDiscardingThreshold", new Class[] { int.class }, discardingThreshold }); + } + + activateAsync(args); + } + + @Override + public void activateAsync(List args) { + AsyncAppender asynAppender = new AsyncAppender(); + + invokeMethod(asynAppender, args); + + asynAppender.setName(productName + "." + logger.getName() + ".AsyncAppender"); + asynAppender.setContext(LogbackLoggerContextUtil.getLoggerContext()); + + Iterator> iterator = logger.iteratorForAppenders(); + boolean hasAppender = false; + while (iterator.hasNext()) { + hasAppender = true; + asynAppender.addAppender(iterator.next()); + } + + if (!hasAppender) { + throw new IllegalStateException("Activate async appender failed, no appender exist."); + } + + asynAppender.start(); + + iterator = logger.iteratorForAppenders(); + while (iterator.hasNext()) { + logger.detachAppender(iterator.next()); + } + + logger.addAppender(asynAppender); + + setProductName(productName); + } + + protected ch.qos.logback.core.Appender getSizeRollingAppender(String productName, String file, String encoding, + String size, int maxBackupIndex) { + RollingFileAppender appender = new RollingFileAppender(); + appender.setContext(LogbackLoggerContextUtil.getLoggerContext()); + + appender.setName(productName + "." + file.replace(File.separatorChar, '.') + ".Appender"); + appender.setAppend(true); + appender.setFile(LoggerHelper.getLogFile(productName, file)); + + SizeBasedTriggeringPolicy triggerPolicy = new SizeBasedTriggeringPolicy(); + setMaxFileSize(triggerPolicy, size); + triggerPolicy.setContext(LogbackLoggerContextUtil.getLoggerContext()); + triggerPolicy.start(); + + FixedWindowRollingPolicy rolling = new FixedWindowRollingPolicy(); + rolling.setContext(LogbackLoggerContextUtil.getLoggerContext()); + rolling.setParent(appender); + rolling.setFileNamePattern(LoggerHelper.getLogFile(productName, file) + ".%i"); + rolling.setParent(appender); + if (maxBackupIndex >= 0) { + rolling.setMaxIndex(maxBackupIndex); + } + rolling.start(); + + appender.setRollingPolicy(rolling); + appender.setTriggeringPolicy(triggerPolicy); + + PatternLayout layout = new PatternLayout(); + layout.setPattern(LoggerHelper.getPattern(productName)); + layout.setContext(LogbackLoggerContextUtil.getLoggerContext()); + layout.start(); + appender.setLayout(layout); + + // 启动 + appender.start(); + return appender; + } + + /** + * logback 1.1.8开始不再支持setMaxFileSize(String)方法 + */ + protected void setMaxFileSize(Object policy, String size) { + try { + try { + Method setMaxFileSizeMethod = policy.getClass().getDeclaredMethod("setMaxFileSize", String.class); + setMaxFileSizeMethod.invoke(policy, size); + } catch (NoSuchMethodException e) { + Method setMaxFileSizeMethod = policy.getClass().getDeclaredMethod("setMaxFileSize", FileSize.class); + setMaxFileSizeMethod.invoke(policy, FileSize.valueOf(size)); + } + } catch (Throwable t) { + throw new RuntimeException("Failed to setMaxFileSize", t); + } + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/option/LogbackActivateOption.java b/client/src/main/java/com/alibaba/nacos/client/logger/option/LogbackActivateOption.java new file mode 100644 index 00000000000..b217e9b6c2a --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/option/LogbackActivateOption.java @@ -0,0 +1,154 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.option; + +import java.io.File; +import java.nio.charset.Charset; + +import com.alibaba.nacos.client.logger.support.LoggerHelper; + +import ch.qos.logback.classic.encoder.PatternLayoutEncoder; +import ch.qos.logback.core.rolling.FixedWindowRollingPolicy; +import ch.qos.logback.core.rolling.RollingFileAppender; +import ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP; +import ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy; +import ch.qos.logback.core.rolling.TimeBasedRollingPolicy; + +/** + * ActivateOption的Logback 0.9.19及后续版本的实现 + * + * @author zhuyong 2014年3月20日 上午10:24:58 + */ +@SuppressWarnings({ "rawtypes", "unchecked" }) +public class LogbackActivateOption extends Logback918ActivateOption { + + public LogbackActivateOption(Object logger) { + super(logger); + } + + protected ch.qos.logback.core.Appender getLogbackDailyRollingFileAppender(String productName, String file, + String encoding) { + RollingFileAppender appender = new RollingFileAppender(); + appender.setContext(LogbackLoggerContextUtil.getLoggerContext()); + + appender.setName(productName + "." + file.replace(File.separatorChar, '.') + ".Appender"); + appender.setAppend(true); + appender.setFile(LoggerHelper.getLogFile(productName, file)); + + TimeBasedRollingPolicy rolling = new TimeBasedRollingPolicy(); + rolling.setParent(appender); + rolling.setFileNamePattern(LoggerHelper.getLogFile(productName, file) + ".%d{yyyy-MM-dd}"); + rolling.setContext(LogbackLoggerContextUtil.getLoggerContext()); + rolling.start(); + appender.setRollingPolicy(rolling); + + PatternLayoutEncoder layout = new PatternLayoutEncoder(); + layout.setPattern(LoggerHelper.getPattern(productName)); + layout.setCharset(Charset.forName(encoding)); + appender.setEncoder(layout); + layout.setContext(LogbackLoggerContextUtil.getLoggerContext()); + layout.start(); + // 启动 + appender.start(); + + return appender; + } + + protected ch.qos.logback.core.Appender getLogbackDailyAndSizeRollingFileAppender(String productName, String file, + String encoding, String size) { + return getLogbackDailyAndSizeRollingFileAppender(productName, file, encoding, size, "yyyy-MM-dd", -1); + } + + protected ch.qos.logback.core.Appender getLogbackDailyAndSizeRollingFileAppender(String productName, String file, + String encoding, String size, + String datePattern, + int maxBackupIndex) { + RollingFileAppender appender = new RollingFileAppender(); + appender.setContext(LogbackLoggerContextUtil.getLoggerContext()); + + appender.setName(productName + "." + file.replace(File.separatorChar, '.') + ".Appender"); + appender.setAppend(true); + appender.setFile(LoggerHelper.getLogFile(productName, file)); + + TimeBasedRollingPolicy rolling = new TimeBasedRollingPolicy(); + rolling.setParent(appender); + if (maxBackupIndex >= 0) { + rolling.setMaxHistory(maxBackupIndex); + } + rolling.setFileNamePattern(LoggerHelper.getLogFile(productName, file) + ".%d{" + datePattern + "}.%i"); + rolling.setContext(LogbackLoggerContextUtil.getLoggerContext()); + + SizeAndTimeBasedFNATP fnatp = new SizeAndTimeBasedFNATP(); + setMaxFileSize(fnatp, size); + fnatp.setTimeBasedRollingPolicy(rolling); + rolling.setTimeBasedFileNamingAndTriggeringPolicy(fnatp); + + rolling.start(); + appender.setRollingPolicy(rolling); + + PatternLayoutEncoder layout = new PatternLayoutEncoder(); + layout.setPattern(LoggerHelper.getPattern(productName)); + layout.setCharset(Charset.forName(encoding)); + appender.setEncoder(layout); + layout.setContext(LogbackLoggerContextUtil.getLoggerContext()); + layout.start(); + + // 启动 + appender.start(); + + return appender; + } + + protected ch.qos.logback.core.Appender getSizeRollingAppender(String productName, String file, String encoding, + String size, int maxBackupIndex) { + RollingFileAppender appender = new RollingFileAppender(); + appender.setContext(LogbackLoggerContextUtil.getLoggerContext()); + + appender.setName(productName + "." + file.replace(File.separatorChar, '.') + ".Appender"); + appender.setAppend(true); + appender.setFile(LoggerHelper.getLogFile(productName, file)); + + SizeBasedTriggeringPolicy triggerPolicy = new SizeBasedTriggeringPolicy(); + setMaxFileSize(triggerPolicy, size); + triggerPolicy.setContext(LogbackLoggerContextUtil.getLoggerContext()); + triggerPolicy.start(); + + FixedWindowRollingPolicy rolling = new FixedWindowRollingPolicy(); + rolling.setContext(LogbackLoggerContextUtil.getLoggerContext()); + rolling.setParent(appender); + rolling.setFileNamePattern(LoggerHelper.getLogFile(productName, file) + ".%i"); + rolling.setParent(appender); + if (maxBackupIndex >= 0) { + rolling.setMaxIndex(maxBackupIndex); + } + rolling.start(); + + appender.setRollingPolicy(rolling); + appender.setTriggeringPolicy(triggerPolicy); + + PatternLayoutEncoder layout = new PatternLayoutEncoder(); + layout.setPattern(LoggerHelper.getPattern(productName)); + layout.setCharset(Charset.forName(encoding)); + appender.setEncoder(layout); + layout.setContext(LogbackLoggerContextUtil.getLoggerContext()); + layout.start(); + + // 启动 + appender.start(); + + return appender; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/option/LogbackLoggerContextUtil.java b/client/src/main/java/com/alibaba/nacos/client/logger/option/LogbackLoggerContextUtil.java new file mode 100644 index 00000000000..2c947ed6291 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/option/LogbackLoggerContextUtil.java @@ -0,0 +1,47 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.option; + +import org.slf4j.ILoggerFactory; +import org.slf4j.LoggerFactory; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.core.LogbackException; +/** + * Logback Context Util + * @author Nacos + * + */ +public class LogbackLoggerContextUtil { + + private static LoggerContext loggerContext = null; + + public static LoggerContext getLoggerContext() { + if (loggerContext == null) { + ILoggerFactory lcObject = LoggerFactory.getILoggerFactory(); + + if (!(lcObject instanceof LoggerContext)) { + throw new LogbackException( + "Expected LOGBACK binding with SLF4J, but another log system has taken the place: " + + lcObject.getClass().getSimpleName()); + } + + loggerContext = (LoggerContext) lcObject; + } + + return loggerContext; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/option/Slf4jLog4j2AdapterActivateOption.java b/client/src/main/java/com/alibaba/nacos/client/logger/option/Slf4jLog4j2AdapterActivateOption.java new file mode 100644 index 00000000000..397d7d34813 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/option/Slf4jLog4j2AdapterActivateOption.java @@ -0,0 +1,68 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.option; + +import java.lang.reflect.Field; + +import com.alibaba.nacos.client.logger.Logger; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +/** + * @author zhuyong on 2017/4/18. + */ +public class Slf4jLog4j2AdapterActivateOption extends Log4j2ActivateOption { + + private static Field loggerField = null; + + static { + try { + loggerField = org.apache.logging.slf4j.Log4jLogger.class.getDeclaredField("logger"); + loggerField.setAccessible(true); + } catch (Exception e) { + throw new RuntimeException("logger must be instanceof org.apache.logging.slf4j.Log4jLogger", e); + } + } + + public Slf4jLog4j2AdapterActivateOption(Object logger) { + super(null); + + try { + org.apache.logging.log4j.core.Logger log4j2Logger = (org.apache.logging.log4j.core.Logger) loggerField.get(logger); + super.logger = log4j2Logger; + super.configuration = super.logger.getContext().getConfiguration(); + } catch (Exception e) { + throw new RuntimeException("logger must be instanceof org.apache.logging.slf4j.Log4jLogger", e); + } + } + + @Override + @SuppressFBWarnings("NM_WRONG_PACKAGE") + public void activateAppender(Logger logger) { + if (!(logger.getDelegate() instanceof org.apache.logging.slf4j.Log4jLogger)) { + throw new IllegalArgumentException( + "logger must be org.apache.logging.slf4j.Log4jLogger, but it's " + + logger.getDelegate().getClass()); + } + + try { + org.apache.logging.log4j.core.Logger log4j2Logger = (org.apache.logging.log4j.core.Logger) loggerField.get(logger.getDelegate()); + super.activateAppender(log4j2Logger); + } catch (Exception e) { + throw new RuntimeException("activateAppender error, ", e); + } + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/option/Slf4jLog4jAdapterActivateOption.java b/client/src/main/java/com/alibaba/nacos/client/logger/option/Slf4jLog4jAdapterActivateOption.java new file mode 100644 index 00000000000..71c0a1956df --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/option/Slf4jLog4jAdapterActivateOption.java @@ -0,0 +1,71 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.option; + +import java.lang.reflect.Field; + +import com.alibaba.nacos.client.logger.Logger; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +/** + * Slf4j-log4j12架构下的ActivateOption实现 + * + * @author zhuyong 2014年3月20日 上午10:26:04 + */ +public class Slf4jLog4jAdapterActivateOption extends Log4jActivateOption { + + private static Field loggerField = null; + + static { + try { + loggerField = org.slf4j.impl.Log4jLoggerAdapter.class.getDeclaredField("logger"); + loggerField.setAccessible(true); + } catch (Exception e) { + throw new RuntimeException("logger must be instanceof org.slf4j.impl.Log4jLoggerAdapter", e); + } + } + + public Slf4jLog4jAdapterActivateOption(Object logger) { + super(null); + + try { + org.apache.log4j.Logger log4jLogger = (org.apache.log4j.Logger) loggerField.get(logger); + super.logger = log4jLogger; + } catch (Exception e) { + throw new RuntimeException("logger must be instanceof org.slf4j.impl.Log4jLoggerAdapter", e); + } + } + + @Override + @SuppressFBWarnings("NM_WRONG_PACKAGE") + public void activateAppender(Logger logger) { + if (!(logger.getDelegate() instanceof org.slf4j.impl.Log4jLoggerAdapter)) { + throw new IllegalArgumentException( + "logger must be org.slf4j.impl.Log4jLoggerAdapter, but it's " + + logger.getDelegate().getClass()); + } + + try { + org.apache.log4j.Logger log4jLogger = + (org.apache.log4j.Logger) loggerField.get(logger.getDelegate()); + super.activateAppender(log4jLogger); + setProductName(logger.getProductName()); + } catch (Exception e) { + throw new RuntimeException("activateAppender error, ", e); + } + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/slf4j/Slf4jLogger.java b/client/src/main/java/com/alibaba/nacos/client/logger/slf4j/Slf4jLogger.java new file mode 100644 index 00000000000..9d5fd03c2df --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/slf4j/Slf4jLogger.java @@ -0,0 +1,178 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.slf4j; + +import java.lang.reflect.Constructor; + +import com.alibaba.nacos.client.logger.Logger; +import com.alibaba.nacos.client.logger.option.ActivateOption; +import com.alibaba.nacos.client.logger.support.LoggerHelper; +import com.alibaba.nacos.client.logger.support.LoggerSupport; +import com.alibaba.nacos.client.logger.util.MessageUtil; +/** + * slf4j logger + * @author Nacos + * + */ +public class Slf4jLogger extends LoggerSupport implements Logger { + + private static boolean CanUseEncoder = false; + private static final String LOGBACK_CLASSNAME = "ch.qos.logback.classic.Logger"; + private static final String SLF4J_CLASSNAME = "org.slf4j.impl.Log4jLoggerAdapter"; + private static final String SLF4JLOG4J_CLASSNAME = "org.apache.logging.slf4j.Log4jLogger"; + static { + try { + // logback从0.9.19开始采用encoder,@see http://logback.qos.ch/manual/encoders.html + Class.forName("ch.qos.logback.classic.encoder.PatternLayoutEncoder"); + CanUseEncoder = true; + } catch (ClassNotFoundException e) { + CanUseEncoder = false; + } + } + + private org.slf4j.Logger delegate; + + @SuppressWarnings("unchecked") + public + Slf4jLogger(org.slf4j.Logger delegate){ + super(delegate); + if (delegate == null) { + throw new IllegalArgumentException("delegate Logger is null"); + } + this.delegate = delegate; + + String activateOptionClass = null; + if (LOGBACK_CLASSNAME.equals(delegate.getClass().getName())) { + if (CanUseEncoder) { + activateOptionClass = "com.alibaba.nacos.client.logger.option.LogbackActivateOption"; + } else { + activateOptionClass = "com.alibaba.nacos.client.logger.option.Logback918ActivateOption"; + } + } else if (SLF4J_CLASSNAME.equals(delegate.getClass().getName())) { + activateOptionClass = "com.alibaba.nacos.client.logger.option.Slf4jLog4jAdapterActivateOption"; + } else if (SLF4JLOG4J_CLASSNAME.equals(delegate.getClass().getName())) { + activateOptionClass = "com.alibaba.nacos.client.logger.option.Slf4jLog4j2AdapterActivateOption"; + } + + try { + Class clazz = (Class) Class.forName(activateOptionClass); + Constructor c = clazz.getConstructor(Object.class); + this.activateOption = c.newInstance(delegate); + } catch (Exception e) { + throw new IllegalArgumentException("delegate must be logback impl or slf4j-log4j impl", e); + } + } + + @Override + public void debug(String context, String message) { + if (isDebugEnabled()) { + message = LoggerHelper.getResourceBundleString(getProductName(), message); + delegate.debug(MessageUtil.getMessage(context, message)); + } + } + + @Override + public void debug(String context, String format, Object... args) { + if (isDebugEnabled()) { + format = LoggerHelper.getResourceBundleString(getProductName(), format); + delegate.debug(MessageUtil.getMessage(context, format), args); + } + } + + @Override + public void info(String context, String message) { + if (isInfoEnabled()) { + message = LoggerHelper.getResourceBundleString(getProductName(), message); + delegate.info(MessageUtil.getMessage(context, message)); + } + } + + @Override + public void info(String context, String format, Object... args) { + if (isInfoEnabled()) { + format = LoggerHelper.getResourceBundleString(getProductName(), format); + delegate.info(MessageUtil.getMessage(context, format), args); + } + } + + @Override + public void warn(String message, Throwable t) { + if (isWarnEnabled()) { + message = LoggerHelper.getResourceBundleString(getProductName(), message); + delegate.warn(MessageUtil.getMessage(null, message), t); + } + } + + @Override + public void warn(String context, String message) { + if (isWarnEnabled()) { + message = LoggerHelper.getResourceBundleString(getProductName(), message); + delegate.warn(MessageUtil.getMessage(context, message)); + } + } + + @Override + public void warn(String context, String format, Object... args) { + if (isWarnEnabled()) { + format = LoggerHelper.getResourceBundleString(getProductName(), format); + delegate.warn(MessageUtil.getMessage(context, format), args); + } + } + + @Override + public void error(String context, String errorCode, String message) { + if (isErrorEnabled()) { + message = LoggerHelper.getResourceBundleString(getProductName(), message); + delegate.error(MessageUtil.getMessage(context, errorCode, message)); + } + } + + @Override + public void error(String context, String errorCode, String message, Throwable t) { + if (isErrorEnabled()) { + message = LoggerHelper.getResourceBundleString(getProductName(), message); + delegate.error(MessageUtil.getMessage(context, errorCode, message), t); + } + } + + @Override + public void error(String context, String errorCode, String format, Object... args) { + if (isErrorEnabled()) { + format = LoggerHelper.getResourceBundleString(getProductName(), format); + delegate.error(MessageUtil.getMessage(context, errorCode, format), args); + } + } + + @Override + public boolean isDebugEnabled() { + return delegate.isDebugEnabled(); + } + + @Override + public boolean isInfoEnabled() { + return delegate.isInfoEnabled(); + } + + @Override + public boolean isWarnEnabled() { + return delegate.isWarnEnabled(); + } + + @Override + public boolean isErrorEnabled() { + return delegate.isErrorEnabled(); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/slf4j/Slf4jLoggerFactory.java b/client/src/main/java/com/alibaba/nacos/client/logger/slf4j/Slf4jLoggerFactory.java new file mode 100644 index 00000000000..95484e1df5b --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/slf4j/Slf4jLoggerFactory.java @@ -0,0 +1,51 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.slf4j; + + +import com.alibaba.nacos.client.logger.Logger; +import com.alibaba.nacos.client.logger.nop.NopLogger; +import com.alibaba.nacos.client.logger.support.ILoggerFactory; +import com.alibaba.nacos.client.logger.support.LogLog; +/** + * Slf4jLogger Factory + * @author Nacos + * + */ +public class Slf4jLoggerFactory implements ILoggerFactory { + + public Slf4jLoggerFactory() throws ClassNotFoundException { + Class.forName("org.slf4j.impl.StaticLoggerBinder"); + } + + public Logger getLogger(String name) { + try { + return new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(name)); + } catch (Throwable t) { + LogLog.error("Failed to get Slf4jLogger", t); + return new NopLogger(); + } + } + + public Logger getLogger(Class clazz) { + try { + return new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(clazz)); + } catch (Throwable t) { + LogLog.error("Failed to get Slf4jLogger", t); + return new NopLogger(); + } + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/support/AppenderInfo.java b/client/src/main/java/com/alibaba/nacos/client/logger/support/AppenderInfo.java new file mode 100644 index 00000000000..074853eaa40 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/support/AppenderInfo.java @@ -0,0 +1,48 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.support; + +import java.util.HashMap; + +/** + * @author zhuyong on 2017/6/30. + */ +public class AppenderInfo extends HashMap { + + private static String name = "name"; + private static String type = "type"; + private static String file = "file"; + + public String getName() { + return (String) get(AppenderInfo.name); + } + + public void setName(String name) { + put(AppenderInfo.name, name); + } + + public void setType(String type) { + put(AppenderInfo.type, type); + } + + public void setFile(String file) { + put(AppenderInfo.file, file); + } + + public void withDetail(String name, Object value) { + put(name, value); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/support/DepthThrowableRenderer.java b/client/src/main/java/com/alibaba/nacos/client/logger/support/DepthThrowableRenderer.java new file mode 100644 index 00000000000..59e4299a424 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/support/DepthThrowableRenderer.java @@ -0,0 +1,90 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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. + */ +/* + * Copyright 2014 Alibaba.com All right reserved. This software is the + * confidential and proprietary information of Alibaba.com ("Confidential + * Information"). You shall not disclose such Confidential Information and shall + * use it only in accordance with the terms of the license agreement you entered + * into with Alibaba.com. + */ +package com.alibaba.nacos.client.logger.support; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.io.LineNumberReader; +import java.io.PrintWriter; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.ArrayList; + +import org.apache.log4j.spi.ThrowableRenderer; + +/** + * 针对 Log4j 1.2.16 及以上版本,提供对异常栈的深度控制 + * + * @author zhuyong 2014年9月19日 上午10:31:48 + */ +public final class DepthThrowableRenderer implements ThrowableRenderer { + + private int depth = -1; + + public DepthThrowableRenderer(int depth) { + this.depth = depth; + } + + public void setDepth(int depth) { + this.depth = depth; + } + + public String[] doRender(final Throwable throwable) { + return render(throwable, depth); + } + + /** + * Render throwable using Throwable.printStackTrace. + * + * @param throwable throwable, may not be null. + * @param depth stack depth + * @return string representation. + */ + public static String[] render(final Throwable throwable, final int depth) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + try { + throwable.printStackTrace(pw); + } catch (RuntimeException ex) { + } + pw.flush(); + LineNumberReader reader = new LineNumberReader(new StringReader(sw.toString())); + ArrayList lines = new ArrayList(); + try { + String line = reader.readLine(); + int count = 0; + while (line != null && (depth == -1 || count++ <= depth)) { + lines.add(line); + line = reader.readLine(); + } + } catch (IOException ex) { + if (ex instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + lines.add(ex.toString()); + } + String[] tempRep = new String[lines.size()]; + lines.toArray(tempRep); + return tempRep; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/support/ErrorLog.java b/client/src/main/java/com/alibaba/nacos/client/logger/support/ErrorLog.java new file mode 100644 index 00000000000..9a6ec14f797 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/support/ErrorLog.java @@ -0,0 +1,35 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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. + */ +/* + * Copyright 2014 Alibaba.com All right reserved. This software is the + * confidential and proprietary information of Alibaba.com ("Confidential + * Information"). You shall not disclose such Confidential Information and shall + * use it only in accordance with the terms of the license agreement you entered + * into with Alibaba.com. + */ +package com.alibaba.nacos.client.logger.support; + +/** + * 兼容老的ErrorLog,后续请使用{@link LoggerHelper} + * + * @author zhuyong 2014年7月1日 上午11:41:22 + */ +public class ErrorLog { + + public static String buildErrorMsg(String msg, String errorCode, String errorType) { + return LoggerHelper.getErrorCodeStr(null, errorCode, errorType, msg); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/support/ILoggerFactory.java b/client/src/main/java/com/alibaba/nacos/client/logger/support/ILoggerFactory.java new file mode 100644 index 00000000000..1c7663e75ed --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/support/ILoggerFactory.java @@ -0,0 +1,44 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.support; + +import com.alibaba.nacos.client.logger.Logger; + +/** + * logger factory interface + * + * @author Nacos + * + */ +public interface ILoggerFactory { + /** + * get logger + * + * @param clazz + * class + * @return logger + */ + Logger getLogger(Class clazz); + + /** + * get logger + * + * @param name + * logger name + * @return logger + */ + Logger getLogger(String name); +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/support/Log4jHelper.java b/client/src/main/java/com/alibaba/nacos/client/logger/support/Log4jHelper.java new file mode 100644 index 00000000000..2597a9ba63c --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/support/Log4jHelper.java @@ -0,0 +1,201 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.support; + +import org.apache.log4j.Appender; +import org.apache.log4j.AsyncAppender; +import org.apache.log4j.ConsoleAppender; +import org.apache.log4j.FileAppender; +import org.apache.log4j.Level; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.log4j.spi.LoggerRepository; +import org.apache.log4j.spi.ThrowableRenderer; +import org.apache.log4j.spi.ThrowableRendererSupport; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author zhuyong on 2017/6/28. + */ +@SuppressWarnings("PMD.AbstractClassShouldStartWithAbstractNamingRule") +public class Log4jHelper { + + private static boolean Log4j = false, Log4jGT1216 = false; + + static { + try { + Class loggerClass = Class.forName("org.apache.log4j.Logger"); + // 这里可能会加载到应用中依赖的log4j,因此需要判断classloader + if (loggerClass.getClassLoader().equals(Log4jHelper.class.getClassLoader())) { + LogManager.getLoggerRepository(); + try { + Class throwableRendererClass = Class.forName("org.apache.log4j.spi.ThrowableRenderer"); + // 这里可能会加载到应用中依赖的log4j 1.2.16版本的类,因此需要额外判断 + if (loggerClass.getClassLoader().equals(throwableRendererClass.getClassLoader()) + && throwableRendererClass.getClassLoader().equals(Log4jHelper.class.getClassLoader())) { + Log4jGT1216 = true; + } + } catch (Throwable t) { + LogLog.warn("log4j must >= 1.2.16 for change throwable depth"); + } + Log4j = true; + } + } catch (Throwable t) { + } + } + @SuppressFBWarnings("NP_BOOLEAN_RETURN_NULL") + public static Boolean setDepth(int depth) { + if (Log4j && Log4jGT1216) { + try { + LoggerRepository repo = LogManager.getLoggerRepository(); + doSetDepth(repo, depth); + return Boolean.TRUE; + } catch (Throwable t) { + // ignore + LogLog.error("failed to set depth for log4j", t); + return Boolean.FALSE; + } + } + + return null; + } + @SuppressFBWarnings("NP_BOOLEAN_RETURN_NULL") + public static Boolean changeLevel(String name, String level) { + if (Log4j) { + Level l = Level.toLevel(level, Level.ERROR); + Logger logger = LogManager.getLoggerRepository().exists(name); + if (logger != null) { + logger.setLevel(l); + LogLog.info("set log4j log level success, " + name + ": " + l); + return true; + } else { + Logger root = LogManager.getLoggerRepository().getRootLogger(); + if (root.getName().equals(name)) { + root.setLevel(l); + LogLog.info("set log4j log level success, " + name + ": " + l); + return true; + } + } + LogLog.info("set log4j log level fail, no logger name exists: " + name); + return false; + } + return null; + } + + public static Map getLoggers(String name) { + Map appenders = new HashMap(10); + if (!Log4j) { + return appenders; + } + + if (name != null && !"".equals(name.trim())) { + Logger logger = LogManager.getLoggerRepository().exists(name); + if (logger != null) { + appenders.put(name, doGetLoggerInfo(logger)); + } + } else { + // 获取所有logger时,如果没有appender则忽略 + Enumeration loggers = LogManager.getLoggerRepository().getCurrentLoggers(); + + if (loggers != null) { + while (loggers.hasMoreElements()) { + Logger logger = loggers.nextElement(); + LoggerInfo info = doGetLoggerInfo(logger); + if (info.getAppenders() == null || !info.getAppenders().isEmpty()) { + appenders.put(logger.getName(), info); + } + } + } + + Logger root = LogManager.getLoggerRepository().getRootLogger(); + if (root != null) { + LoggerInfo info = doGetLoggerInfo(root); + if (info.getAppenders() == null || !info.getAppenders().isEmpty()) { + appenders.put(root.getName(), info); + } + } + } + + return appenders; + } + + private static LoggerInfo doGetLoggerInfo(Logger logger) { + LoggerInfo info = new LoggerInfo(logger.getName(), logger.getAdditivity()); + Level level = logger.getLevel(), effectiveLevel = logger.getEffectiveLevel(); + if (level != null) { + info.setLevel(level.toString()); + } + if (effectiveLevel != null) { + info.setEffectiveLevel(effectiveLevel.toString()); + } + + List result = doGetLoggerAppenders(logger.getAllAppenders()); + info.setAppenders(result); + return info; + } + + private static List doGetLoggerAppenders(Enumeration appenders) { + List result = new ArrayList(); + + while (appenders.hasMoreElements()) { + AppenderInfo info = new AppenderInfo(); + Appender appender = appenders.nextElement(); + + info.setName(appender.getName()); + info.setType(appender.getClass().getName()); + + result.add(info); + if (appender instanceof FileAppender) { + info.setFile(((FileAppender) appender).getFile()); + } else if (appender instanceof ConsoleAppender) { + info.withDetail("target", ((ConsoleAppender) appender).getTarget()); + } else if (appender instanceof AsyncAppender) { + List asyncs = doGetLoggerAppenders(((AsyncAppender) appender).getAllAppenders()); + // 标明异步appender + List nestedNames = new ArrayList(); + for (AppenderInfo a : asyncs) { + nestedNames.add(a.getName()); + result.add(a); + } + info.withDetail("nestedNames", nestedNames); + } + } + + return result; + } + + private static void doSetDepth(LoggerRepository repo, int depth) { + if (repo instanceof ThrowableRendererSupport) { + Object tr = ((ThrowableRendererSupport) repo).getThrowableRenderer(); + if (tr == null || !(tr instanceof DepthThrowableRenderer)) { + Object ctr = new DepthThrowableRenderer(depth); + // 自定义ThrowableRender,栈深度设置 + ((ThrowableRendererSupport) repo).setThrowableRenderer((ThrowableRenderer) ctr); + LogLog.info("set log4j log depth success, depth: " + depth); + } else { + ((DepthThrowableRenderer) tr).setDepth(depth); + LogLog.info("set log4j log depth success, depth: " + depth); + } + } + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/support/LogLog.java b/client/src/main/java/com/alibaba/nacos/client/logger/support/LogLog.java new file mode 100644 index 00000000000..9c7779ac23c --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/support/LogLog.java @@ -0,0 +1,143 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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. + */ +/* + * Copyright 2001-2004 The Apache Software Foundation. + * + * Licensed 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 com.alibaba.nacos.client.logger.support; + +import java.io.PrintStream; +import java.util.Calendar; +/** + * logger log + * @author Nacos + * + */ +public class LogLog { + + private static final String CLASS_INFO = LogLog.class.getClassLoader().toString(); + + private static boolean debugEnabled = false; + private static boolean infoEnabled = true; + + private static boolean quietMode = false; + + private static final String DEBUG_PREFIX = "JM.Log:DEBUG "; + private static final String INFO_PREFIX = "JM.Log:INFO "; + + private static final String WARN_PREFIX = "JM.Log:WARN "; + private static final String ERR_PREFIX = "JM.Log:ERROR "; + + public static void setQuietMode(boolean quietMode) { + LogLog.quietMode = quietMode; + } + + static public void setInternalDebugging(boolean enabled) { + debugEnabled = enabled; + } + + static public void setInternalInfoing(boolean enabled) { + infoEnabled = enabled; + } + + public static void debug(String msg) { + if (debugEnabled && !quietMode) { + println(System.out, DEBUG_PREFIX + msg); + } + } + + public static void debug(String msg, Throwable t) { + if (debugEnabled && !quietMode) { + println(System.out, DEBUG_PREFIX + msg); + if (t != null) { + t.printStackTrace(System.out); + } + } + } + + public static void info(String msg) { + if (infoEnabled && !quietMode) { + println(System.out, INFO_PREFIX + msg); + } + } + + public static void info(String msg, Throwable t) { + if (infoEnabled && !quietMode) { + println(System.out, INFO_PREFIX + msg); + if (t != null) { + t.printStackTrace(System.out); + } + } + } + + public static void error(String msg) { + if (quietMode) { + return; + } + + println(System.err, ERR_PREFIX + msg); + } + + public static void error(String msg, Throwable t) { + if (quietMode) { + return; + } + + println(System.err, ERR_PREFIX + msg); + if (t != null) { + t.printStackTrace(); + } + } + + public static void warn(String msg) { + if (quietMode) { + return; + } + + println(System.err, WARN_PREFIX + msg); + } + + public static void warn(String msg, Throwable t) { + if (quietMode) { + return; + } + + println(System.err, WARN_PREFIX + msg); + if (t != null) { + t.printStackTrace(); + } + } + + private static void println(PrintStream out, String msg) { + out.println(Calendar.getInstance().getTime().toString() + " " + CLASS_INFO + " " + msg); + } + + private static void outPrintln(PrintStream out, String msg) { + out.println(Calendar.getInstance().getTime().toString() + " " + CLASS_INFO + " " + msg); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/support/LogbackHelper.java b/client/src/main/java/com/alibaba/nacos/client/logger/support/LogbackHelper.java new file mode 100644 index 00000000000..8028f3ee6ad --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/support/LogbackHelper.java @@ -0,0 +1,226 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.support; + +import ch.qos.logback.classic.AsyncAppender; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.encoder.PatternLayoutEncoder; +import ch.qos.logback.classic.pattern.ThrowableProxyConverter; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import ch.qos.logback.core.AsyncAppenderBase; +import ch.qos.logback.core.ConsoleAppender; +import ch.qos.logback.core.FileAppender; +import ch.qos.logback.core.Layout; +import ch.qos.logback.core.OutputStreamAppender; +import ch.qos.logback.core.encoder.Encoder; +import ch.qos.logback.core.encoder.LayoutWrappingEncoder; +import ch.qos.logback.core.pattern.Converter; +import ch.qos.logback.core.pattern.PatternLayoutBase; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import org.slf4j.ILoggerFactory; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * @author zhuyong on 2017/6/28. + */ +public class LogbackHelper { + + private static boolean Logback = false; + private static Field f, f1; + private static ILoggerFactory lcObject; + + static { + try { + Class loggerClass = Class.forName("ch.qos.logback.classic.Logger"); + // 这里可能会加载到应用中依赖的logback,因此需要判断classloader + if (loggerClass.getClassLoader().equals(LogbackHelper.class.getClassLoader())) { + ILoggerFactory lc = org.slf4j.LoggerFactory.getILoggerFactory(); + + if (!(lc instanceof LoggerContext)) { + LogLog.warn("expected logback binding with SLF4J, but another log system has taken the place: " + lcObject.getClass().getSimpleName()); + } else { + lcObject = lc; + + f = PatternLayoutBase.class.getDeclaredField("head"); + f.setAccessible(true); + + f1 = ThrowableProxyConverter.class.getDeclaredField("lengthOption"); + f1.setAccessible(true); + + Logback = true; + } + } + } catch (Throwable t) { + LogLog.error("failed to init LogbackHelper, " + t.getMessage()); + } + } + @SuppressFBWarnings("NP_BOOLEAN_RETURN_NULL") + public static Boolean setDepth(int depth) { + if (Logback) { + if (depth == -1) { + depth = Integer.MAX_VALUE; + } + try { + LoggerContext loggerContext = (LoggerContext) lcObject; + + List loggers = loggerContext.getLoggerList(); + for (ch.qos.logback.classic.Logger logger : loggers) { + Iterator> iter = logger.iteratorForAppenders(); + doSetDepth(iter, depth); + } + } catch (Throwable t) { + LogLog.error("failed to set depth for logback", t); + return false; + } + LogLog.info("set logback throwable depth success, depth: " + depth); + return true; + } + return null; + } + @SuppressFBWarnings("NP_BOOLEAN_RETURN_NULL") + public static Boolean changeLevel(String name, String level) { + if (Logback) { + try { + Level l = Level.toLevel(level, Level.ERROR); + LoggerContext loggerContext = (LoggerContext) lcObject; + + Logger logger = loggerContext.exists(name); + if (logger != null) { + logger.setLevel(l); + LogLog.info("set logback log level success, " + name + ": " + l); + return true; + } + LogLog.info("set logback log level fail, no logger name exists: " + name); + } catch (Throwable t) { + LogLog.error("failed to change level for logback, " + name + ": " + level, t); + } + return false; + } + return null; + } + + public static Map getLoggers(String name) { + Map appenders = new HashMap(10); + + if (Logback) { + LoggerContext loggerContext = (LoggerContext) lcObject; + if (name != null && !"".equals(name.trim())) { + Logger logger = loggerContext.exists(name); + if (logger != null) { + appenders.put(name, doGetLoggerInfo(logger)); + } + } else { + // 获取所有logger时,如果没有appender则忽略 + List loggers = loggerContext.getLoggerList(); + for (Logger logger : loggers) { + LoggerInfo info = doGetLoggerInfo(logger); + if (info.getAppenders() == null || !info.getAppenders().isEmpty()) { + appenders.put(logger.getName(), info); + } + } + } + } + + return appenders; + } + + private static void doSetDepth(Iterator> iter, int depth) + throws IllegalAccessException { + while (iter.hasNext()) { + Appender a = iter.next(); + if (a instanceof AsyncAppenderBase) { + Iterator> aiter = ((AsyncAppenderBase) a).iteratorForAppenders(); + doSetDepth(aiter, depth); + } else if (a instanceof OutputStreamAppender) { + OutputStreamAppender oa = (OutputStreamAppender) a; + Encoder e = oa.getEncoder(); + Layout l = null; + if (e instanceof PatternLayoutEncoder) { + l = ((PatternLayoutEncoder) e).getLayout(); + } else if (e instanceof LayoutWrappingEncoder) { + l = ((LayoutWrappingEncoder) e).getLayout(); + } + if (l != null) { + if (l instanceof PatternLayoutBase) { + Converter c = (Converter) f.get(l); + while (c != null) { + if (c instanceof ThrowableProxyConverter) { + f1.set(c, depth); + break; + } + c = c.getNext(); + } + } + } + } + } + } + + private static LoggerInfo doGetLoggerInfo(Logger logger) { + LoggerInfo info = new LoggerInfo(logger.getName(), logger.isAdditive()); + Level level = logger.getLevel(), effectiveLevel = logger.getEffectiveLevel(); + if (level != null) { + info.setLevel(level.toString()); + } + if (effectiveLevel != null) { + info.setEffectiveLevel(effectiveLevel.toString()); + } + + List result = doGetLoggerAppenders(logger.iteratorForAppenders()); + info.setAppenders(result); + return info; + } + + private static List doGetLoggerAppenders(Iterator> appenders) { + List result = new ArrayList(); + + while (appenders.hasNext()) { + AppenderInfo info = new AppenderInfo(); + Appender appender = appenders.next(); + info.setName(appender.getName()); + info.setType(appender.getClass().getName()); + if (appender instanceof FileAppender) { + info.setFile(((FileAppender) appender).getFile()); + } else if (appender instanceof AsyncAppender) { + AsyncAppender aa = (AsyncAppender) appender; + Iterator> iter = aa.iteratorForAppenders(); + List asyncs = doGetLoggerAppenders(iter); + // 标明异步appender + List nestedNames = new ArrayList(); + for (AppenderInfo a : asyncs) { + nestedNames.add(a.getName()); + result.add(a); + } + info.withDetail("nestedNames", nestedNames); + } else if (appender instanceof ConsoleAppender) { + info.withDetail("target", ((ConsoleAppender) appender).getTarget()); + } + result.add(info); + } + + return result; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/support/LoggerHelper.java b/client/src/main/java/com/alibaba/nacos/client/logger/support/LoggerHelper.java new file mode 100644 index 00000000000..01a7bba8610 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/support/LoggerHelper.java @@ -0,0 +1,252 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.support; + +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import ch.qos.logback.core.FileAppender; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.io.File; +import java.util.Iterator; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.concurrent.ConcurrentHashMap; + +import com.alibaba.nacos.client.logger.Logger; + +/** + * logger help + * @author Nacos + * + */ +@SuppressWarnings("PMD.AbstractClassShouldStartWithAbstractNamingRule") +public abstract class LoggerHelper { + + private static final String MORE_URL_POSFIX = ".ERROR_CODE_MORE_URL"; + private static final String DEFAULT_MORE_URL = "http://console.taobao.net/help/"; + + private static String LOG_PATH = null; + private static final String CONVERSION_PATTERN = "01 %d{yyyy-MM-dd HH:mm:ss.SSS} %p [%-5t:%c{2}] %m%n"; + + private static Map Product_Logger_Info; + private static Map Product_Logger_Pattern; + + private static Map Product_Resource_Bundle; + + static { + String dpath = System.getProperty("JM.LOG.PATH"); + if (dpath == null || dpath.trim().equals("")) { + String defaultPath = System.getProperty("user.home"); + LOG_PATH = defaultPath + File.separator + "logs" + File.separator; + } else { + if (!new File(dpath).isAbsolute()) { +// throw new RuntimeException("-DJM.LOG.PATH must be an absolute path."); + String defaultPath = System.getProperty("user.home"); + dpath = defaultPath + File.separator + dpath; + } + if (dpath.endsWith(File.separator)) { + LOG_PATH = dpath; + } else { + LOG_PATH = dpath + File.separator; + } + } + + LogLog.info("Log root path: " + LOG_PATH); + + Product_Logger_Info = new ConcurrentHashMap(); + Product_Logger_Pattern = new ConcurrentHashMap(); + Product_Resource_Bundle = new ConcurrentHashMap(); + } + + /** + * 获取中间件日志根目录,以File.separator结尾 + */ + public static String getLogpath() { + return LOG_PATH; + } + + /** + *
+     * 获取中间件产品日志路径
+     * 
+     * 优先使用-DJM.LOG.PATH参数,且必须是绝对路径
+     * 其次是{user.home}/logs/
+     * 
+     * 比如hsf调用:LoggerHelper.getLogFile("hsf", "hsf.log"),则返回{user.home}/logs/hsf/hsf.log
+     * 
+ * + * @param productName 中间件产品名,如hsf, tddl + * @param fileName 日志文件名,如hsf.log,如需要二级子目录,可以传 subDir + File.separator + *.log + */ + public static String getLogFile(String productName, String fileName) { + String file = LOG_PATH + productName + File.separator + fileName; + + if (Product_Logger_Info.get(productName) == null) { + Product_Logger_Info.put(productName, true); + LogLog.info("Set " + productName + " log path: " + LOG_PATH + productName); + } + + return file; + } + + /** + * 获取中间件日志格式,优先使用用户产品自定义的格式,logback/log4j通用 + */ + public static String getPattern(String productName) { + String pattern = Product_Logger_Pattern.get(productName); + if (pattern == null) { + return CONVERSION_PATTERN; + } + + return pattern; + } + + /** + * 获取中间件日志特定格式 + */ + public static String getPattern() { + return CONVERSION_PATTERN; + } + + /** + * 设置特定中间件产品的日志格式,注意,这里的格式需要自己保证在 log4j/logback 下都兼容,框架不做校验,同时控制台输出仍会采用中间件的特定格式 + * + * @param productName 中间件产品名,如hsf, tddl + * @param pattern 日志格式 + */ + public static void setPattern(String productName, String pattern) { + Product_Logger_Pattern.put(productName, pattern); + } + + /** + * 设置产品的日志国际化properties文件 + * + * @param productName 中间件产品名,如hsf, tddl + * @param bundleName bundleName + */ + public static void setResourceBundle(String productName, String bundleName) { + try { + ResourceBundle rb = ResourceBundle.getBundle(bundleName); + Product_Resource_Bundle.put(productName, rb); + } catch (Exception e) { + LogLog.error("Failed to set " + productName + " resource bundle for: " + bundleName, e); + } + } + + /** + * 获取国际化的message,如果找不到,则返回原始的code + * + * @param productName 中间件产品名,如hsf, tddl + * @param code code + */ + public static String getResourceBundleString(String productName, String code) { + if (Product_Resource_Bundle.isEmpty() || code == null || productName == null) { + return code; + } + + ResourceBundle rs = Product_Resource_Bundle.get(productName); + if (rs != null) { + try { + String value = rs.getString(code); + return value; + } catch (MissingResourceException e) { + return code; + } + } + + return code; + } + + /** + * 获取统一格式的ErrorCode输出 + * + * @param errorCode + */ + @Deprecated + public static String getErrorCodeStr(String errorCode) { + return "ERR-CODE: [" + errorCode + "], More: [" + "http://console.taobao.net/jm/" + errorCode + "]"; + } + + /** + * 根据productName获取统一格式的ErrorCode输出 + * + * @param productName 如 HSF,会根据 HSF.ErrorCodeMoreUrl 从 System属性中获取 more url 前缀,如http://console.taobao.net/jm/ + * @param errorCode 错误码,如HSF-001 + * @param errorType 错误类型 + * @param message 出错异常信息 + */ + public static String getErrorCodeStr(String productName, String errorCode, String errorType, String message) { + String moreUrl = DEFAULT_MORE_URL; + if (productName != null) { + String customUrl = System.getProperty(productName.toUpperCase() + MORE_URL_POSFIX); + + if (customUrl != null) { + moreUrl = customUrl; + } + } + + StringBuilder sb = new StringBuilder(); + sb.append(message); + sb.append(" ERR-CODE: ["); + sb.append(errorCode); + sb.append("], Type: ["); + sb.append(errorType); + sb.append("], More: ["); + sb.append(moreUrl); + sb.append(errorCode); + sb.append("]"); + + return sb.toString(); + } + + @SuppressFBWarnings(value = { "RV_RETURN_VALUE_IGNORED_BAD_PRACTICE" }) + public static String getLogFileP(String productName, String fileName) { + String file = getLogFile(productName, fileName); + File logfile = new File(file); + logfile.getParentFile().mkdirs(); + return file; + } + + /** + * When prudent is set to true, file appenders from multiple JVMs can safely + * write to the same file. + * + * Only support by logback + * + * @param prudent + * @since 0.1.8 + */ + public static void activePrudent(Logger logger, boolean prudent) { + if (logger != null && logger.getDelegate() != null) { + if (!(logger.getDelegate() instanceof ch.qos.logback.classic.Logger)) { + throw new IllegalArgumentException("logger must be ch.qos.logback.classic.Logger, but it's " + + logger.getDelegate().getClass()); + } + + Iterator> iter = ((ch.qos.logback.classic.Logger) logger.getDelegate()).iteratorForAppenders(); + while (iter.hasNext()) { + ch.qos.logback.core.Appender appender = iter.next(); + if (appender instanceof FileAppender) { + ((FileAppender) appender).setPrudent(prudent); + } else { + continue; + } + } + } + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/support/LoggerInfo.java b/client/src/main/java/com/alibaba/nacos/client/logger/support/LoggerInfo.java new file mode 100644 index 00000000000..9f484e07c0a --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/support/LoggerInfo.java @@ -0,0 +1,54 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.support; + +import java.util.HashMap; +import java.util.List; + +/** + * @author zhuyong on 2017/06/29 + */ +public class LoggerInfo extends HashMap { + + private static String level = "level"; + private static String effectiveLevel = "effectiveLevel"; + private static String additivity = "additivity"; + private static String appenders = "appenders"; + + public LoggerInfo(String name, boolean additivity) { + put(LoggerInfo.additivity, additivity); + } + + public void setLevel(String level) { + put(LoggerInfo.level, level); + } + + public void setEffectiveLevel(String effectiveLevel) { + put(LoggerInfo.effectiveLevel, effectiveLevel); + } + + public String getLevel() { + return (String) get(level); + } + + public List getAppenders() { + return (List) get(appenders); + } + + public void setAppenders(List appenders) { + put(LoggerInfo.appenders, appenders); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/support/LoggerSupport.java b/client/src/main/java/com/alibaba/nacos/client/logger/support/LoggerSupport.java new file mode 100644 index 00000000000..6d459666b5e --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/support/LoggerSupport.java @@ -0,0 +1,198 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.support; + +import java.util.List; +import java.util.Map; + +import com.alibaba.nacos.client.logger.Level; +import com.alibaba.nacos.client.logger.Logger; +import com.alibaba.nacos.client.logger.option.ActivateOption; +/** + * Logger Support + * @author Nacos + * + */ +@SuppressWarnings("PMD.AbstractClassShouldStartWithAbstractNamingRule") +public abstract class LoggerSupport implements Logger { + + protected Object delegateLogger; + protected ActivateOption activateOption; + + public LoggerSupport(Object delegate) { + this.delegateLogger = delegate; + } + + @Override + public void debug(String message) { + debug(null, message); + } + + @Override + public void debug(String format, Object... args) { + debug(null, format, args); + } + + @Override + public void info(String message) { + info(null, message); + } + + @Override + public void info(String format, Object... args) { + info(null, format, args); + } + + @Override + public void warn(String message) { + warn(null, message); + } + + @Override + public void warn(String format, Object... args) { + warn(null, format, args); + } + + @Override + public void error(String errorCode, String message) { + error(null, errorCode, message); + } + + @Override + public void error(String errorCode, String message, Throwable t) { + error(null, errorCode, message, t); + } + + @Override + public void error(String errorCode, String format, Object... args) { + error(null, errorCode, format, args); + } + + public Object getDelegate() { + return delegateLogger; + } + + public void activateConsoleAppender(String target, String encoding) { + if (activateOption != null) { + activateOption.activateConsoleAppender(target, encoding); + } + } + + @Override + public void activateAppender(String productName, String file, String encoding) { + if (activateOption != null) { + activateOption.activateAppender(productName, file, encoding); + } + } + + @Override + public void setLevel(Level level) { + if (activateOption != null) { + activateOption.setLevel(level); + } + } + + @Override + public Level getLevel() { + if (activateOption != null) { + return activateOption.getLevel(); + } + return null; + } + + @Override + public void setAdditivity(boolean additivity) { + if (activateOption != null) { + activateOption.setAdditivity(additivity); + } + } + + @Override + public String getProductName() { + if (activateOption != null) { + return activateOption.getProductName(); + } + + return null; + } + + @Override + public void activateAsyncAppender(String productName, String file, String encoding) { + if (activateOption != null) { + activateOption.activateAsyncAppender(productName, file, encoding); + } + } + + @Override + public void activateAsyncAppender(String productName, String file, String encoding, int queueSize, int discardingThreshold) { + if (activateOption != null) { + activateOption.activateAsyncAppender(productName, file, encoding, queueSize, discardingThreshold); + } + } + + @Override + public void activateAppenderWithTimeAndSizeRolling(String productName, String file, String encoding, String size) { + if (activateOption != null) { + activateOption.activateAppenderWithTimeAndSizeRolling(productName, file, encoding, size); + } + } + + @Override + public void activateAppender(Logger logger) { + if (activateOption != null) { + activateOption.activateAppender(logger); + } + } + + @Override + public void activateAppenderWithTimeAndSizeRolling(String productName, String file, String encoding, String size, + String datePattern) { + if (activateOption != null) { + activateOption.activateAppenderWithTimeAndSizeRolling(productName, file, encoding, size, datePattern); + } + } + + @Override + public void activateAppenderWithTimeAndSizeRolling(String productName, String file, String encoding, String size, + String datePattern, int maxBackupIndex) { + if (activateOption != null) { + activateOption.activateAppenderWithTimeAndSizeRolling(productName, file, encoding, size, datePattern, + maxBackupIndex); + } + } + + @Override + public void activateAppenderWithSizeRolling(String productName, String file, String encoding, String size, + int maxBackupIndex) { + if (activateOption != null) { + activateOption.activateAppenderWithSizeRolling(productName, file, encoding, size, maxBackupIndex); + } + } + + @Override + public void activateAsync(int queueSize, int discardingThreshold) { + if (activateOption != null) { + activateOption.activateAsync(queueSize, discardingThreshold); + } + } + + @Override + public void activateAsync(List args) { + if (activateOption != null) { + activateOption.activateAsync(args); + } + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/util/FormattingTuple.java b/client/src/main/java/com/alibaba/nacos/client/logger/util/FormattingTuple.java new file mode 100644 index 00000000000..91c4a3783dd --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/util/FormattingTuple.java @@ -0,0 +1,68 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.util; + +/** + * Holds the results of formatting done by {@link MessageFormatter}. + * + * @author Joern Huxhorn + */ +public class FormattingTuple { + + + static public FormattingTuple NULL = new FormattingTuple(null); + + private String message; + private Throwable throwable; + private Object[] argArray; + + public FormattingTuple(String message) { + this(message, null, null); + } + + public FormattingTuple(String message, Object[] argArray, Throwable throwable) { + this.message = message; + this.throwable = throwable; + if(throwable == null) { + this.argArray = argArray.clone(); + } else { + this.argArray = trimmedCopy(argArray); + } + } + + static Object[] trimmedCopy(Object[] argArray) { + if(argArray == null || argArray.length == 0) { + throw new IllegalStateException("non-sensical empty or null argument array"); + } + final int trimemdLen = argArray.length -1; + Object[] trimmed = new Object[trimemdLen]; + System.arraycopy(argArray, 0, trimmed, 0, trimemdLen); + return trimmed; + } + + public String getMessage() { + return message; + } + + public Object[] getArgArray() { + return argArray.clone(); + } + + public Throwable getThrowable() { + return throwable; + } + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/util/MessageFormatter.java b/client/src/main/java/com/alibaba/nacos/client/logger/util/MessageFormatter.java new file mode 100644 index 00000000000..0faa5c9d3c7 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/util/MessageFormatter.java @@ -0,0 +1,421 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.util; + +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; + +// contributors: lizongbo: proposed special treatment of array parameter values +// Joern Huxhorn: pointed out double[] omission, suggested deep array copy +/** + * Formats messages according to very simple substitution rules. Substitutions + * can be made 1, 2 or more arguments. + * + *

+ * For example, + * + *

+ * MessageFormatter.format("Hi {}.", "there")
+ * 
+ * + * will return the string "Hi there.". + *

+ * The {} pair is called the formatting anchor. It serves to designate + * the location where arguments need to be substituted within the message + * pattern. + *

+ * In case your message contains the '{' or the '}' character, you do not have + * to do anything special unless the '}' character immediately follows '{'. For + * example, + * + *

+ * MessageFormatter.format("Set {1,2,3} is not equal to {}.", "1,2");
+ * 
+ * + * will return the string "Set {1,2,3} is not equal to 1,2.". + * + *

+ * If for whatever reason you need to place the string "{}" in the message + * without its formatting anchor meaning, then you need to escape the + * '{' character with '\', that is the backslash character. Only the '{' + * character should be escaped. There is no need to escape the '}' character. + * For example, + * + *

+ * MessageFormatter.format("Set \\{} is not equal to {}.", "1,2");
+ * 
+ * + * will return the string "Set {} is not equal to 1,2.". + * + *

+ * The escaping behavior just described can be overridden by escaping the escape + * character '\'. Calling + * + *

+ * MessageFormatter.format("File name is C:\\\\{}.", "file.zip");
+ * 
+ * + * will return the string "File name is C:\file.zip". + * + *

+ * The formatting conventions are different than those of {@link MessageFormat} + * which ships with the Java platform. This is justified by the fact that + * SLF4J's implementation is 10 times faster than that of {@link MessageFormat}. + * This local performance difference is both measurable and significant in the + * larger context of the complete logging processing chain. + * + *

+ * See also {@link #format(String, Object)}, + * {@link #format(String, Object, Object)} and + * {@link #arrayFormat(String, Object[])} methods for more details. + * + * @author Ceki Gülcü + * @author Joern Huxhorn + */ +final public class MessageFormatter { + static final char DELIM_START = '{'; + static final char DELIM_STOP = '}'; + static final String DELIM_STR = "{}"; + private static final char ESCAPE_CHAR = '\\'; + private static int DELIMETER_START_INDEX = 2; + /** + * Performs single argument substitution for the 'messagePattern' passed as + * parameter. + *

+ * For example, + * + *

+   * MessageFormatter.format("Hi {}.", "there");
+   * 
+ * + * will return the string "Hi there.". + *

+ * + * @param messagePattern + * The message pattern which will be parsed and formatted + * @param argument + * The argument to be substituted in place of the formatting anchor + * @return The formatted message + */ + final public static FormattingTuple format(String messagePattern, Object arg) { + return arrayFormat(messagePattern, new Object[] { arg }); + } + + /** + * + * Performs a two argument substitution for the 'messagePattern' passed as + * parameter. + *

+ * For example, + * + *

+   * MessageFormatter.format("Hi {}. My name is {}.", "Alice", "Bob");
+   * 
+ * + * will return the string "Hi Alice. My name is Bob.". + * + * @param messagePattern + * The message pattern which will be parsed and formatted + * @param arg1 + * The argument to be substituted in place of the first formatting + * anchor + * @param arg2 + * The argument to be substituted in place of the second formatting + * anchor + * @return The formatted message + */ + final public static FormattingTuple format(final String messagePattern, + Object arg1, Object arg2) { + return arrayFormat(messagePattern, new Object[] { arg1, arg2 }); + } + + static final Throwable getThrowableCandidate(Object[] argArray) { + if (argArray == null || argArray.length == 0) { + return null; + } + + final Object lastEntry = argArray[argArray.length - 1]; + if (lastEntry instanceof Throwable) { + return (Throwable) lastEntry; + } + return null; + } + + /** + * Same principle as the {@link #format(String, Object)} and + * {@link #format(String, Object, Object)} methods except that any number of + * arguments can be passed in an array. + * + * @param messagePattern + * The message pattern which will be parsed and formatted + * @param argArray + * An array of arguments to be substituted in place of formatting + * anchors + * @return The formatted message + */ + final public static FormattingTuple arrayFormat(final String messagePattern, + final Object[] argArray) { + + Throwable throwableCandidate = getThrowableCandidate(argArray); + + if (messagePattern == null) { + return new FormattingTuple(null, argArray, throwableCandidate); + } + + if (argArray == null) { + return new FormattingTuple(messagePattern); + } + + int i = 0; + int j; + StringBuffer sbuf = new StringBuffer(messagePattern.length() + 50); + + int lenTmp; + for (lenTmp = 0; lenTmp < argArray.length; lenTmp++) { + + j = messagePattern.indexOf(DELIM_STR, i); + + if (j == -1) { + // no more variables + if (i == 0) { + return new FormattingTuple(messagePattern, argArray, + throwableCandidate); + } else { + sbuf.append(messagePattern.substring(i, messagePattern.length())); + return new FormattingTuple(sbuf.toString(), argArray, + throwableCandidate); + } + } else { + if (isEscapedDelimeter(messagePattern, j)) { + if (!isDoubleEscaped(messagePattern, j)) { + lenTmp--; // DELIM_START was escaped, thus should not be incremented + sbuf.append(messagePattern.substring(i, j - 1)); + sbuf.append(DELIM_START); + i = j + 1; + } else { + // The escape character preceding the delimiter start is + // itself escaped: "abc x:\\{}" + // we have to consume one backward slash + sbuf.append(messagePattern.substring(i, j - 1)); + deeplyAppendParameter(sbuf, argArray[lenTmp], new HashMap(10)); + i = j + 2; + } + } else { + // normal case + sbuf.append(messagePattern.substring(i, j)); + deeplyAppendParameter(sbuf, argArray[lenTmp], new HashMap(10)); + i = j + 2; + } + } + } + // append the characters following the last {} pair. + sbuf.append(messagePattern.substring(i, messagePattern.length())); + if (lenTmp < argArray.length - 1) { + return new FormattingTuple(sbuf.toString(), argArray, throwableCandidate); + } else { + return new FormattingTuple(sbuf.toString(), argArray, null); + } + } + + final static boolean isEscapedDelimeter(String messagePattern, + int delimeterStartIndex) { + + if (delimeterStartIndex == 0) { + return false; + } + char potentialEscape = messagePattern.charAt(delimeterStartIndex - 1); + if (potentialEscape == ESCAPE_CHAR) { + return true; + } else { + return false; + } + } + + final static boolean isDoubleEscaped(String messagePattern, + int delimeterStartIndex) { + if (delimeterStartIndex >= DELIMETER_START_INDEX + && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR) { + return true; + } else { + return false; + } + } + + + private static void deeplyAppendParameter(StringBuffer sbuf, Object o, + Map seenMap) { + if (o == null) { + sbuf.append("null"); + return; + } + if (!o.getClass().isArray()) { + safeObjectAppend(sbuf, o); + } else { + // check for primitive array types because they + // unfortunately cannot be cast to Object[] + if (o instanceof boolean[]) { + booleanArrayAppend(sbuf, (boolean[]) o); + } else if (o instanceof byte[]) { + byteArrayAppend(sbuf, (byte[]) o); + } else if (o instanceof char[]) { + charArrayAppend(sbuf, (char[]) o); + } else if (o instanceof short[]) { + shortArrayAppend(sbuf, (short[]) o); + } else if (o instanceof int[]) { + intArrayAppend(sbuf, (int[]) o); + } else if (o instanceof long[]) { + longArrayAppend(sbuf, (long[]) o); + } else if (o instanceof float[]) { + floatArrayAppend(sbuf, (float[]) o); + } else if (o instanceof double[]) { + doubleArrayAppend(sbuf, (double[]) o); + } else { + objectArrayAppend(sbuf, (Object[]) o, seenMap); + } + } + } + + private static void safeObjectAppend(StringBuffer sbuf, Object o) { + try { + String oAsString = o.toString(); + sbuf.append(oAsString); + } catch (Throwable t) { + System.err + .println("SLF4J: Failed toString() invocation on an object of type [" + + o.getClass().getName() + "]"); + t.printStackTrace(); + sbuf.append("[FAILED toString()]"); + } + + } + + private static void objectArrayAppend(StringBuffer sbuf, Object[] a, + Map seenMap) { + sbuf.append('['); + if (!seenMap.containsKey(a)) { + seenMap.put(a, null); + final int len = a.length; + for (int i = 0; i < len; i++) { + deeplyAppendParameter(sbuf, a[i], seenMap); + if (i != len - 1) { + sbuf.append(", "); + } + } + // allow repeats in siblings + seenMap.remove(a); + } else { + sbuf.append("..."); + } + sbuf.append(']'); + } + + private static void booleanArrayAppend(StringBuffer sbuf, boolean[] a) { + sbuf.append('['); + final int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + private static void byteArrayAppend(StringBuffer sbuf, byte[] a) { + sbuf.append('['); + final int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + private static void charArrayAppend(StringBuffer sbuf, char[] a) { + sbuf.append('['); + final int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + private static void shortArrayAppend(StringBuffer sbuf, short[] a) { + sbuf.append('['); + final int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + private static void intArrayAppend(StringBuffer sbuf, int[] a) { + sbuf.append('['); + final int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + private static void longArrayAppend(StringBuffer sbuf, long[] a) { + sbuf.append('['); + final int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + private static void floatArrayAppend(StringBuffer sbuf, float[] a) { + sbuf.append('['); + final int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + private static void doubleArrayAppend(StringBuffer sbuf, double[] a) { + sbuf.append('['); + final int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/logger/util/MessageUtil.java b/client/src/main/java/com/alibaba/nacos/client/logger/util/MessageUtil.java new file mode 100644 index 00000000000..766b0730c73 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/logger/util/MessageUtil.java @@ -0,0 +1,47 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.logger.util; +/** + * Error msg format + * @author Nacos + * + */ +public class MessageUtil { + + public static String formatMessage(String format, Object[] argArray) { + FormattingTuple ft = MessageFormatter.arrayFormat(format, argArray); + return ft.getMessage(); + } + + public static String getMessage(String message) { + return getMessage(null, message); + } + + public static String getMessage(String context, String message) { + return getMessage(context, null, message); + } + + public static String getMessage(String context, String errorCode, String message) { + if (context == null) { + context = ""; + } + + if (errorCode == null) { + errorCode = ""; + } + return "[" + context + "] [] [" + errorCode + "] " + message; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java b/client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java new file mode 100644 index 00000000000..1e7a21a03a8 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java @@ -0,0 +1,242 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.naming; + +import com.alibaba.nacos.api.PropertyKeyConst; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.naming.NamingService; +import com.alibaba.nacos.api.naming.listener.EventListener; +import com.alibaba.nacos.api.naming.pojo.Cluster; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.client.naming.beat.BeatInfo; +import com.alibaba.nacos.client.naming.beat.BeatReactor; +import com.alibaba.nacos.client.naming.core.*; +import com.alibaba.nacos.client.naming.net.NamingProxy; +import com.alibaba.nacos.client.naming.utils.CollectionUtils; +import com.alibaba.nacos.client.naming.utils.StringUtils; +import com.alibaba.nacos.client.naming.utils.UtilAndComs; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Properties; + +/** + * @author dungu.zpf + */ +@SuppressWarnings("PMD.ServiceOrDaoClassShouldEndWithImplRule") +public class NacosNamingService implements NamingService { + + /** + * Each Naming instance should have different namespace. + */ + private String namespace; + + private String endpoint; + + private String serverList; + + private String cacheDir; + + private String logName; + + private HostReactor hostReactor; + + private BeatReactor beatReactor; + + private EventDispatcher eventDispatcher; + + private NamingProxy serverProxy; + + private void init() { + + namespace = System.getProperty(PropertyKeyConst.NAMESPACE); + + if (StringUtils.isEmpty(namespace)) { + namespace = UtilAndComs.DEFAULT_NAMESPACE_ID; + } + + logName = System.getProperty(UtilAndComs.NACOS_NAMING_LOG_NAME); + if (StringUtils.isEmpty(logName)) { + logName = "naming.log"; + } + + cacheDir = System.getProperty("com.alibaba.nacos.naming.cache.dir"); + if (StringUtils.isEmpty(cacheDir)) { + cacheDir = System.getProperty("user.home") + "/nacos/naming/" + namespace; + } + } + + public NacosNamingService(String serverList) { + + this.serverList = serverList; + init(); + eventDispatcher = new EventDispatcher(); + serverProxy = new NamingProxy(namespace, endpoint, serverList); + beatReactor = new BeatReactor(serverProxy); + hostReactor = new HostReactor(eventDispatcher, serverProxy, cacheDir); + } + + public NacosNamingService(Properties properties) { + + init(); + + serverList = properties.getProperty(PropertyKeyConst.SERVER_ADDR); + + if (StringUtils.isNotEmpty(properties.getProperty(PropertyKeyConst.NAMESPACE))) { + namespace = properties.getProperty(PropertyKeyConst.NAMESPACE); + } + + if (StringUtils.isNotEmpty(properties.getProperty(UtilAndComs.NACOS_NAMING_LOG_NAME))) { + logName = properties.getProperty(UtilAndComs.NACOS_NAMING_LOG_NAME); + } + + if (StringUtils.isNotEmpty(properties.getProperty(PropertyKeyConst.ENDPOINT))) { + endpoint = properties.getProperty(PropertyKeyConst.ENDPOINT) + ":" + + properties.getProperty("address.server.port", "8080"); + } + + cacheDir = System.getProperty("user.home") + "/nacos/naming/" + namespace; + + eventDispatcher = new EventDispatcher(); + serverProxy = new NamingProxy(namespace, endpoint, serverList); + beatReactor = new BeatReactor(serverProxy); + hostReactor = new HostReactor(eventDispatcher, serverProxy, cacheDir); + + } + + @Override + public void registerInstance(String serviceName, String ip, int port) throws NacosException { + registerInstance(serviceName, ip, port, StringUtils.EMPTY); + } + + @Override + public void registerInstance(String serviceName, String ip, int port, String clusterName) throws NacosException { + Instance instance = new Instance(); + instance.setIp(ip); + instance.setPort(port); + instance.setWeight(1.0); + instance.setCluster(new Cluster(clusterName)); + + registerInstance(serviceName, instance); + } + + @Override + public void registerInstance(String serviceName, Instance instance) throws NacosException { + + BeatInfo beatInfo = new BeatInfo(); + beatInfo.setDom(serviceName); + beatInfo.setIp(instance.getIp()); + beatInfo.setPort(instance.getPort()); + beatInfo.setCluster(instance.getCluster().getName()); + + beatReactor.addBeatInfo(serviceName, beatInfo); + + serverProxy.registerService(serviceName, instance); + } + + @Override + public void deregisterInstance(String serviceName, String ip, int port) throws NacosException { + deregisterInstance(serviceName, ip, port, StringUtils.EMPTY); + } + + @Override + public void deregisterInstance(String serviceName, String ip, int port, String clusterName) throws NacosException { + beatReactor.removeBeatInfo(serviceName); + serverProxy.deregisterService(serviceName, ip, port, clusterName); + } + + @Override + public List getAllInstances(String serviceName) throws NacosException { + return getAllInstances(serviceName, new ArrayList()); + } + + @Override + public List getAllInstances(String serviceName, List clusters) throws NacosException { + + Domain domain = hostReactor.getDom(serviceName, StringUtils.join(clusters, ","), StringUtils.EMPTY, false); + List list; + if (domain == null || CollectionUtils.isEmpty(list = domain.getHosts())) { + throw new IllegalStateException("no host to srv for dom: " + serviceName); + } + return list; + } + + @Override + public List selectInstances(String serviceName, boolean healthyOnly) throws NacosException { + return selectInstances(serviceName, new ArrayList(), healthyOnly); + } + + @Override + public List selectInstances(String serviceName, List clusters, boolean healthy) throws NacosException { + + Domain domain = hostReactor.getDom(serviceName, StringUtils.join(clusters, ","), StringUtils.EMPTY, false); + List list; + if (domain == null || CollectionUtils.isEmpty(list = domain.getHosts())) { + throw new IllegalStateException("no host to srv for dom: " + serviceName); + } + + if (healthy) { + Iterator iterator = list.iterator(); + while (iterator.hasNext()) { + Instance instance = iterator.next(); + if (!instance.isHealthy()) { + iterator.remove(); + } + } + } else { + Iterator iterator = list.iterator(); + while (iterator.hasNext()) { + Instance instance = iterator.next(); + if (instance.isHealthy()) { + iterator.remove(); + } + } + } + + return list; + } + + @Override + public Instance selectOneHealthyInstance(String serviceName) { + return selectOneHealthyInstance(serviceName, new ArrayList()); + } + + @Override + public Instance selectOneHealthyInstance(String serviceName, List clusters) { + return Balancer.RandomByWeight.selectHost(hostReactor.getDom(serviceName, StringUtils.join(clusters, ","))); + } + + @Override + public void subscribe(String service, EventListener listener) { + eventDispatcher.addListener(hostReactor.getDom(service, StringUtils.EMPTY), StringUtils.EMPTY, listener); + } + + @Override + public void subscribe(String service, List clusters, EventListener listener) { + eventDispatcher.addListener(hostReactor.getDom(service, StringUtils.join(clusters, ",")), StringUtils.join(clusters, ","), listener); + } + + @Override + public void unsubscribe(String service, EventListener listener) { + eventDispatcher.removeListener(service, StringUtils.EMPTY, listener); + } + + @Override + public void unsubscribe(String service, List clusters, EventListener listener) { + eventDispatcher.removeListener(service, StringUtils.join(clusters, ","), listener); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/backups/FailoverReactor.java b/client/src/main/java/com/alibaba/nacos/client/naming/backups/FailoverReactor.java new file mode 100644 index 00000000000..4a0cbc59803 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/backups/FailoverReactor.java @@ -0,0 +1,242 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.naming.backups; + +import com.alibaba.fastjson.JSON; +import com.alibaba.nacos.client.naming.cache.ConcurrentDiskUtil; +import com.alibaba.nacos.client.naming.cache.DiskCache; +import com.alibaba.nacos.client.naming.core.Domain; +import com.alibaba.nacos.client.naming.core.HostReactor; +import com.alibaba.nacos.client.naming.utils.CollectionUtils; +import com.alibaba.nacos.client.naming.utils.LogUtils; +import com.alibaba.nacos.client.naming.utils.StringUtils; +import com.alibaba.nacos.client.naming.utils.UtilAndComs; + +import java.io.BufferedReader; +import java.io.File; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.util.*; +import java.util.concurrent.*; + +/** + * @author dungu.zpf + */ +public class FailoverReactor { + + private String failoverDir; + + private HostReactor hostReactor; + + public FailoverReactor(HostReactor hostReactor, String cacheDir) { + this.hostReactor = hostReactor; + this.failoverDir = cacheDir + "/failover"; + this.init(); + } + + private Map domainMap = new ConcurrentHashMap(); + private ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r); + thread.setDaemon(true); + thread.setName("com.alibaba.nacos.naming.failover"); + return thread; + } + }); + + private Map switchParams = new ConcurrentHashMap(); + private static final long DAY_PERIOD_MINUTES = 24 * 60; + + public void init() { + + executorService.scheduleWithFixedDelay(new SwitchRefresher(), 0L, 5000L, TimeUnit.MILLISECONDS); + + executorService.scheduleWithFixedDelay(new DiskFileWriter(), 30, DAY_PERIOD_MINUTES, TimeUnit.MINUTES); + + // backup file on startup if failover directory is empty. + executorService.schedule(new Runnable() { + @Override + public void run() { + try { + File cacheDir = new File(failoverDir); + + if (!cacheDir.exists() && !cacheDir.mkdirs()) { + throw new IllegalStateException("failed to create cache dir: " + failoverDir); + } + + File[] files = cacheDir.listFiles(); + if (files == null || files.length <= 0) { + new DiskFileWriter().run(); + } + } catch (Throwable e) { + LogUtils.LOG.error("NA", "failed to backup file on startup.", e); + } + + } + }, 10000L, TimeUnit.MILLISECONDS); + } + + public Date addDay(Date date, int num) { + Calendar startDT = Calendar.getInstance(); + startDT.setTime(date); + startDT.add(Calendar.DAY_OF_MONTH, num); + return startDT.getTime(); + } + + class SwitchRefresher implements Runnable { + long lastModifiedMillis = 0L; + + @Override + public void run() { + try { + File switchFile = new File(failoverDir + UtilAndComs.FAILOVER_SWITCH); + if (!switchFile.exists()) { + switchParams.put("failover-mode", "false"); + LogUtils.LOG.debug("failover switch is not found, " + switchFile.getName()); + return; + } + + long modified = switchFile.lastModified(); + + if (lastModifiedMillis < modified) { + lastModifiedMillis = modified; + String failover = ConcurrentDiskUtil.getFileContent(failoverDir + UtilAndComs.FAILOVER_SWITCH, Charset.defaultCharset().toString()); + if (!StringUtils.isEmpty(failover)) { + List lines = Arrays.asList(failover.split(DiskCache.getLineSeperator())); + + for (String line : lines) { + String line1 = line.trim(); + if ("1".equals(line1)) { + switchParams.put("failover-mode", "true"); + LogUtils.LOG.info("failover-mode is on"); + new FailoverFileReader().run(); + } else if ("0".equals(line1)) { + switchParams.put("failover-mode", "false"); + LogUtils.LOG.info("failover-mode is off"); + } + } + } else { + switchParams.put("failover-mode", "false"); + } + } + + } catch (Throwable e) { + LogUtils.LOG.error("NA", "failed to read failover switch.", e); + } + } + } + + class FailoverFileReader implements Runnable { + + @Override + public void run() { + Map domMap = new HashMap(16); + + BufferedReader reader = null; + try { + + File cacheDir = new File(failoverDir); + if (!cacheDir.exists() && !cacheDir.mkdirs()) { + throw new IllegalStateException("failed to create cache dir: " + failoverDir); + } + + File[] files = cacheDir.listFiles(); + if (files == null) { + return; + } + + for (File file : files) { + if (!file.isFile()) { + continue; + } + + if (file.getName().equals(UtilAndComs.FAILOVER_SWITCH)) { + continue; + } + + Domain dom = new Domain(file.getName()); + + try { + String dataString = ConcurrentDiskUtil.getFileContent(file, Charset.defaultCharset().toString()); + reader = new BufferedReader(new StringReader(dataString)); + + String json; + if ((json = reader.readLine()) != null) { + try { + dom = JSON.parseObject(json, Domain.class); + } catch (Exception e) { + LogUtils.LOG.error("NA", "error while parsing cached dom : " + json, e); + } + } + + } catch (Exception e) { + LogUtils.LOG.error("NA", "failed to read cache for dom: " + file.getName(), e); + } finally { + try { + if (reader != null) { + reader.close(); + } + } catch (Exception e) { + //ignore + } + } + if (!CollectionUtils.isEmpty(dom.getHosts())) { + domMap.put(dom.getKey(), dom); + } + } + } catch (Exception e) { + LogUtils.LOG.error("NA", "failed to read cache file", e); + } + + if (domMap.size() > 0) { + domainMap = domMap; + } + } + } + + class DiskFileWriter extends TimerTask { + public void run() { + Map map = hostReactor.getDomMap(); + for (Map.Entry entry : map.entrySet()) { + Domain domain = entry.getValue(); + if (StringUtils.equals(domain.getKey(), UtilAndComs.ALL_IPS) || StringUtils.equals(domain.getName(), UtilAndComs.ENV_LIST_KEY) + || StringUtils.equals(domain.getName(), "00-00---000-ENV_CONFIGS-000---00-00") + || StringUtils.equals(domain.getName(), "vipclient.properties") + || StringUtils.equals(domain.getName(), "00-00---000-ALL_HOSTS-000---00-00")) { + continue; + } + + DiskCache.write(domain, failoverDir); + } + } + } + + public boolean isFailoverSwitch() { + return Boolean.parseBoolean(switchParams.get("failover-mode")); + } + + public Domain getDom(String key) { + Domain domain = domainMap.get(key); + + if (domain == null) { + domain = new Domain(); + domain.setName(key); + } + + return domain; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/beat/BeatInfo.java b/client/src/main/java/com/alibaba/nacos/client/naming/beat/BeatInfo.java new file mode 100644 index 00000000000..f5a3fa87acd --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/beat/BeatInfo.java @@ -0,0 +1,66 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.naming.beat; + +import com.alibaba.fastjson.JSON; + +/** + * @author dungu.zpf + */ +public class BeatInfo { + + private int port; + private String ip; + private String dom; + private String cluster; + + @Override + public String toString() { + return JSON.toJSONString(this); + } + + public String getDom() { + return dom; + } + + public void setDom(String dom) { + this.dom = dom; + } + + public String getCluster() { + return cluster; + } + + public void setCluster(String cluster) { + this.cluster = cluster; + } + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/beat/BeatReactor.java b/client/src/main/java/com/alibaba/nacos/client/naming/beat/BeatReactor.java new file mode 100644 index 00000000000..80f43abebc9 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/beat/BeatReactor.java @@ -0,0 +1,108 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.naming.beat; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.nacos.client.naming.net.NamingProxy; +import com.alibaba.nacos.client.naming.utils.LogUtils; +import com.alibaba.nacos.client.naming.utils.UtilAndComs; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.*; + +/** + * @author harold + */ +public class BeatReactor { + + private ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r); + thread.setDaemon(true); + thread.setName("com.alibaba.nacos.naming.beat.sender"); + return thread; + } + }); + + private long clientBeatInterval = 10 * 1000; + + private NamingProxy serverProxy; + + public final Map dom2Beat = new ConcurrentHashMap(); + + public BeatReactor(NamingProxy serverProxy) { + this.serverProxy = serverProxy; + executorService.execute(new BeatProcessor()); + } + + public void addBeatInfo(String dom, BeatInfo beatInfo) { + dom2Beat.put(dom, beatInfo); + } + + public void removeBeatInfo(String dom) { + dom2Beat.remove(dom); + } + + class BeatProcessor implements Runnable { + + @Override + public void run() { + while (true) { + try { + for (Map.Entry entry : dom2Beat.entrySet()) { + BeatInfo beatInfo = entry.getValue(); + executorService.schedule(new BeatTask(beatInfo), 0, TimeUnit.MILLISECONDS); + LogUtils.LOG.info("BEAT", "send beat to server: ", beatInfo.toString()); + } + + TimeUnit.MILLISECONDS.sleep(clientBeatInterval); + } catch (Exception e) { + LogUtils.LOG.error("CLIENT-BEAT", "Exception while scheduling beat.", e); + } + } + } + } + + class BeatTask implements Runnable { + BeatInfo beatInfo; + + public BeatTask(BeatInfo beatInfo) { + this.beatInfo = beatInfo; + } + + @Override + public void run() { + Map params = new HashMap(2); + params.put("beat", JSON.toJSONString(beatInfo)); + params.put("dom", beatInfo.getDom()); + + try { + String result = serverProxy.callAllServers(UtilAndComs.NACOS_URL_BASE + "/api/clientBeat", params); + JSONObject jsonObject = JSON.parseObject(result); + + if (jsonObject != null) { + clientBeatInterval = jsonObject.getLong("clientBeatInterval"); + + } + } catch (Exception e) { + LogUtils.LOG.error("CLIENT-BEAT", "failed to send beat: " + JSON.toJSONString(beatInfo), e); + } + } + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/cache/ConcurrentDiskUtil.java b/client/src/main/java/com/alibaba/nacos/client/naming/cache/ConcurrentDiskUtil.java new file mode 100644 index 00000000000..114a651586f --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/cache/ConcurrentDiskUtil.java @@ -0,0 +1,238 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.naming.cache; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; + +import com.alibaba.nacos.client.logger.Logger; +import com.alibaba.nacos.client.naming.utils.LogUtils; + +/** + * @author dungu.zpf + */ +public class ConcurrentDiskUtil { + + /** + * get file content + * + * @param path + * file path + * @param charsetName + * charsetName + * @return content + * @throws IOException + * IOException + */ + public static String getFileContent(String path, String charsetName) + throws IOException { + File file = new File(path); + return getFileContent(file, charsetName); + } + + /** + * get file content + * + * @param file + * file + * @param charsetName + * charsetName + * @return content + * @throws IOException + * IOException + */ + public static String getFileContent(File file, String charsetName) + throws IOException { + RandomAccessFile fis = null; + FileLock rlock = null; + try { + fis = new RandomAccessFile(file, "r"); + FileChannel fcin = fis.getChannel(); + int i = 0; + do { + try { + rlock = fcin.tryLock(0L, Long.MAX_VALUE, true); + } catch (Exception e) { + ++i; + if (i > RETRY_COUNT) { + log.error("NA", "read " + file.getName() + " fail;retryed time: " + i, e); + throw new IOException("read " + file.getAbsolutePath() + + " conflict"); + } + sleep(SLEEP_BASETIME * i); + log.warn("read " + file.getName() + " conflict;retry time: " + i); + } + } while (null == rlock); + int fileSize = (int) fcin.size(); + ByteBuffer byteBuffer = ByteBuffer.allocate(fileSize); + fcin.read(byteBuffer); + byteBuffer.flip(); + return byteBufferToString(byteBuffer, charsetName); + } finally { + if (rlock != null) { + rlock.release(); + rlock = null; + } + if (fis != null) { + fis.close(); + fis = null; + } + } + } + + /** + * write file content + * + * @param path + * file path + * @param content + * content + * @param charsetName + * charsetName + * @return whether write ok + * @throws IOException + * IOException + */ + public static Boolean writeFileContent(String path, String content, + String charsetName) throws IOException { + File file = new File(path); + return writeFileContent(file, content, charsetName); + } + + /** + * write file content + * + * @param file + * file + * @param content + * content + * @param charsetName + * charsetName + * @return whether write ok + * @throws IOException + * IOException + */ + public static Boolean writeFileContent(File file, String content, + String charsetName) throws IOException { + + if (!file.exists() && !file.createNewFile()) { + return false; + } + FileChannel channel = null; + FileLock lock = null; + RandomAccessFile raf = null; + try { + raf = new RandomAccessFile(file, "rw"); + channel = raf.getChannel(); + int i = 0; + do { + try { + lock = channel.tryLock(); + } catch (Exception e) { + ++i; + if (i > RETRY_COUNT) { + log.error("NA","write " + file.getName() + " fail;retryed time: " + i); + throw new IOException("write " + file.getAbsolutePath() + + " conflict", e); + } + sleep(SLEEP_BASETIME * i); + log.warn("write " + file.getName() + " conflict;retry time: " + i); + } + } while (null == lock); + + ByteBuffer sendBuffer = ByteBuffer.wrap(content + .getBytes(charsetName)); + while (sendBuffer.hasRemaining()) { + channel.write(sendBuffer); + } + channel.truncate(content.length()); + } catch (FileNotFoundException e) { + throw new IOException("file not exist"); + } finally { + if (lock != null) { + try { + lock.release(); + lock = null; + } catch (IOException e) { + log.warn("close wrong", e); + } + } + if (channel != null) { + try { + channel.close(); + channel = null; + } catch (IOException e) { + log.warn("close wrong", e); + } + } + if (raf != null) { + try { + raf.close(); + raf = null; + } catch (IOException e) { + log.warn("close wrong", e); + } + } + + } + return true; + } + + /** + * transfer ByteBuffer to String + * + * @param buffer + * buffer + * @param charsetName + * charsetName + * @return String + * @throws IOException + * IOException + */ + public static String byteBufferToString(ByteBuffer buffer, + String charsetName) throws IOException { + Charset charset = null; + CharsetDecoder decoder = null; + CharBuffer charBuffer = null; + charset = Charset.forName(charsetName); + decoder = charset.newDecoder(); + charBuffer = decoder.decode(buffer.asReadOnlyBuffer()); + return charBuffer.toString(); + } + + private static void sleep(int time) { + try { + Thread.sleep(time); + } catch (InterruptedException e) { + log.warn("sleep wrong", e); + } + } + + public static void main(String[] args) { + } + + static final public Logger log = LogUtils.LOG; + static final int RETRY_COUNT = 10; + static final int SLEEP_BASETIME = 10; +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/cache/DiskCache.java b/client/src/main/java/com/alibaba/nacos/client/naming/cache/DiskCache.java new file mode 100644 index 00000000000..7b102fb7868 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/cache/DiskCache.java @@ -0,0 +1,151 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.naming.cache; + +import com.alibaba.fastjson.JSON; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.client.naming.core.Domain; +import com.alibaba.nacos.client.naming.utils.CollectionUtils; +import com.alibaba.nacos.client.naming.utils.LogUtils; +import com.alibaba.nacos.client.naming.utils.StringUtils; + +import java.io.BufferedReader; +import java.io.File; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author xuanyin + */ +public class DiskCache { + + public static void write(Domain dom, String dir) { + + try { + makeSureCacheDirExists(dir); + + File file = new File(dir, dom.getKey()); + if (!file.exists()) { + // add another !file.exists() to avoid conflicted creating-new-file from multi-instances + if (!file.createNewFile() && !file.exists()) { + throw new IllegalStateException("failed to create cache file"); + } + } + + StringBuilder keyContentBuffer = new StringBuilder(""); + + String json = dom.getJsonFromServer(); + + if (StringUtils.isEmpty(json)) { + json = JSON.toJSONString(dom); + } + + keyContentBuffer.append(json); + + //Use the concurrent API to ensure the consistency. + ConcurrentDiskUtil.writeFileContent(file, keyContentBuffer.toString(), Charset.defaultCharset().toString()); + + } catch (Throwable e) { + LogUtils.LOG.error("NA", "failed to write cache for dom:" + dom.getName(), e); + } + } + + public static String getLineSeperator() { + String lineSeparator = System.getProperty("line.separator"); + return lineSeparator; + } + + public static Map read(String cacheDir) { + Map domMap = new HashMap(16); + + BufferedReader reader = null; + try { + File[] files = makeSureCacheDirExists(cacheDir).listFiles(); + if (files == null) { + return domMap; + } + + for (File file : files) { + if (!file.isFile()) { + continue; + } + + if (!(file.getName().endsWith(Domain.SPLITER + "meta") || file.getName().endsWith(Domain.SPLITER + "special-url"))) { + Domain dom = new Domain(file.getName()); + List ips = new ArrayList(); + dom.setHosts(ips); + + Domain newFormat = null; + + try { + String dataString = ConcurrentDiskUtil.getFileContent(file, Charset.defaultCharset().toString()); + reader = new BufferedReader(new StringReader(dataString)); + + String json; + while ((json = reader.readLine()) != null) { + try { + if (!json.startsWith("{")) { + continue; + } + + newFormat = JSON.parseObject(json, Domain.class); + + if (StringUtils.isEmpty(newFormat.getName())) { + ips.add(JSON.parseObject(json, Instance.class)); + } + } catch (Throwable e) { + LogUtils.LOG.error("NA", "error while parsing cache file: " + json, e); + } + } + } catch (Exception e) { + LogUtils.LOG.error("NA", "failed to read cache for dom: " + file.getName(), e); + } finally { + try { + if (reader != null) { + reader.close(); + } + } catch (Exception e) { + //ignore + } + } + if (newFormat != null && !StringUtils.isEmpty(newFormat.getName()) && !CollectionUtils.isEmpty(newFormat.getHosts())) { + domMap.put(dom.getKey(), newFormat); + } else if (!CollectionUtils.isEmpty(dom.getHosts())) { + domMap.put(dom.getKey(), dom); + } + } + + } + } catch (Throwable e) { + LogUtils.LOG.error("NA", "failed to read cache file", e); + } + + return domMap; + } + + private static File makeSureCacheDirExists(String dir) { + File cacheDir = new File(dir); + if (!cacheDir.exists() && !cacheDir.mkdirs()) { + throw new IllegalStateException("failed to create cache dir: " + dir); + } + + return cacheDir; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/core/Balancer.java b/client/src/main/java/com/alibaba/nacos/client/naming/core/Balancer.java new file mode 100644 index 00000000000..2395bea4554 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/core/Balancer.java @@ -0,0 +1,98 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.naming.core; + +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.client.naming.utils.Chooser; +import com.alibaba.nacos.client.naming.utils.CollectionUtils; +import com.alibaba.nacos.client.naming.utils.LogUtils; +import com.alibaba.nacos.client.naming.utils.Pair; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicLong; + +/** + * @author xuanyin + */ +public class Balancer { + + /** + * report status to server + */ + public final static List UNCONSISTENT_DOM_WITH_ADDRESS_SERVER = new CopyOnWriteArrayList(); + + public static class RandomByWeight { + + public static List selectAll(Domain dom) { + List hosts = nothing(dom); + + if (CollectionUtils.isEmpty(hosts)) { + throw new IllegalStateException("no host to srv for dom: " + dom.getName()); + } + + return hosts; + } + + public static Instance selectHost(Domain dom) { + + List hosts = selectAll(dom); + + if (CollectionUtils.isEmpty(hosts)) { + throw new IllegalStateException("no host to srv for dom: " + dom.getName()); + } + + return getHostByRandomWeight(hosts); + } + + public static List nothing(Domain dom) { + return dom.getHosts(); + } + } + + /** + * Return one host from the host list by random-weight. + * + * @param hosts The list of the host. + * @return The random-weight result of the host + */ + protected static Instance getHostByRandomWeight(List hosts) { + LogUtils.LOG.debug("entry randomWithWeight"); + if (hosts == null || hosts.size() == 0) { + LogUtils.LOG.debug("hosts == null || hosts.size() == 0"); + return null; + } + + Chooser vipChooser = new Chooser("www.taobao.com"); + + LogUtils.LOG.debug("new Chooser"); + + List> hostsWithWeight = new ArrayList>(); + for (Instance host : hosts) { + if (host.isHealthy()) { + hostsWithWeight.add(new Pair(host, host.getWeight())); + } + } + LogUtils.LOG.debug("for (Host host : hosts)"); + vipChooser.refresh(hostsWithWeight); + LogUtils.LOG.debug("vipChooser.refresh"); + Instance host = vipChooser.randomWithWeight(); + return host; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/core/Domain.java b/client/src/main/java/com/alibaba/nacos/client/naming/core/Domain.java new file mode 100644 index 00000000000..8241867a943 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/core/Domain.java @@ -0,0 +1,245 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.naming.core; + +import com.alibaba.fastjson.annotation.JSONField; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.client.naming.utils.CollectionUtils; +import com.alibaba.nacos.client.naming.utils.StringUtils; +import com.alibaba.nacos.client.naming.utils.UtilAndComs; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author dungu.zpf + */ +public class Domain { + @JSONField(serialize = false) + private String jsonFromServer = StringUtils.EMPTY; + public static final String SPLITER = "@@"; + + @JSONField(name = "dom") + private String name; + + private String clusters; + + private long cacheMillis = 1000L; + + @JSONField(name = "hosts") + private List hosts = new ArrayList(); + + private long lastRefTime = 0L; + + private String checksum = StringUtils.EMPTY; + + private String env = StringUtils.EMPTY; + + private volatile boolean allIPs = false; + + public Domain() { + } + + public boolean isAllIPs() { + return allIPs; + } + + public void setAllIPs(boolean allIPs) { + this.allIPs = allIPs; + } + + public Domain(String key) { + + int maxKeySectionCount = 4; + int allIpFlagIndex = 3; + int envIndex = 2; + int clusterIndex = 1; + int domNameIndex = 0; + + String[] keys = key.split(SPLITER); + if (keys.length >= maxKeySectionCount) { + this.name = keys[domNameIndex]; + this.clusters = keys[clusterIndex]; + this.env = keys[envIndex]; + if (StringUtils.equals(keys[allIpFlagIndex], UtilAndComs.ALL_IPS)) { + this.setAllIPs(true); + } + } else if (keys.length >= allIpFlagIndex) { + this.name = keys[domNameIndex]; + this.clusters = keys[clusterIndex]; + if (StringUtils.equals(keys[envIndex], UtilAndComs.ALL_IPS)) { + this.setAllIPs(true); + } else { + this.env = keys[envIndex]; + } + } else if (keys.length >= envIndex) { + this.name = keys[domNameIndex]; + if (StringUtils.equals(keys[clusterIndex], UtilAndComs.ALL_IPS)) { + this.setAllIPs(true); + } else { + this.clusters = keys[clusterIndex]; + } + } + + this.name = keys[0]; + } + + public Domain(String name, String clusters) { + this(name, clusters, StringUtils.EMPTY); + } + + public Domain(String name, String clusters, String env) { + this.name = name; + this.clusters = clusters; + this.env = env; + } + + public int ipCount() { + return hosts.size(); + } + + public boolean expired() { + return System.currentTimeMillis() - lastRefTime > cacheMillis; + } + + public void setHosts(List hosts) { + this.hosts = hosts; + } + + public boolean isValid() { + return hosts != null; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public void setLastRefTime(long lastRefTime) { + this.lastRefTime = lastRefTime; + } + + public long getLastRefTime() { + return lastRefTime; + } + + public String getClusters() { + return clusters; + } + + public void setClusters(String clusters) { + this.clusters = clusters; + } + + public long getCacheMillis() { + return cacheMillis; + } + + public void setCacheMillis(long cacheMillis) { + this.cacheMillis = cacheMillis; + } + + public List getHosts() { + + return new ArrayList<>(hosts); + } + + public boolean validate() { + if (isAllIPs()) { + return true; + } + + if (CollectionUtils.isEmpty(hosts)) { + return false; + } + + List validHosts = new ArrayList(); + for (Instance host : hosts) { + if (!host.isHealthy()) { + continue; + } + + for (int i = 0; i < host.getWeight(); i++) { + validHosts.add(host); + } + } + + if (CollectionUtils.isEmpty(validHosts)) { + return false; + } + + return true; + } + + @JSONField(serialize = false) + public String getJsonFromServer() { + return jsonFromServer; + } + + public void setJsonFromServer(String jsonFromServer) { + this.jsonFromServer = jsonFromServer; + } + + @JSONField(serialize = false) + public String getKey() { + return getKey(name, clusters, env, isAllIPs()); + } + + @JSONField(serialize = false) + public static String getKey(String name, String clusters, String unit) { + return getKey(name, clusters, unit, false); + } + + @JSONField(serialize = false) + public static String getKey(String name, String clusters, String unit, boolean isAllIPs) { + + if (StringUtils.isEmpty(unit)) { + unit = StringUtils.EMPTY; + } + + if (!StringUtils.isEmpty(clusters) && !StringUtils.isEmpty(unit)) { + return isAllIPs ? name + SPLITER + clusters + SPLITER + unit + SPLITER + UtilAndComs.ALL_IPS + : name + SPLITER + clusters + SPLITER + unit; + } + + if (!StringUtils.isEmpty(clusters)) { + return isAllIPs ? name + SPLITER + clusters + SPLITER + UtilAndComs.ALL_IPS : name + SPLITER + clusters; + } + + if (!StringUtils.isEmpty(unit)) { + return isAllIPs ? name + SPLITER + StringUtils.EMPTY + SPLITER + unit + SPLITER + UtilAndComs.ALL_IPS : + name + SPLITER + StringUtils.EMPTY + SPLITER + unit; + } + + return isAllIPs ? name + SPLITER + UtilAndComs.ALL_IPS : name; + } + + @Override + public String toString() { + return getKey(); + } + + public String getChecksum() { + return checksum; + } + + public void setChecksum(String checksum) { + this.checksum = checksum; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/core/EventDispatcher.java b/client/src/main/java/com/alibaba/nacos/client/naming/core/EventDispatcher.java new file mode 100644 index 00000000000..34625dfc648 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/core/EventDispatcher.java @@ -0,0 +1,136 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.naming.core; + +import com.alibaba.nacos.api.naming.listener.EventListener; +import com.alibaba.nacos.api.naming.listener.NamingEvent; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.client.naming.utils.CollectionUtils; +import com.alibaba.nacos.client.naming.utils.LogUtils; +import com.alibaba.nacos.client.naming.utils.StringUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.*; + +/** + * @author xuanyin + */ +public class EventDispatcher { + + private ExecutorService executor = null; + + private BlockingQueue changedDoms = new LinkedBlockingQueue(); + + private ConcurrentMap> observerMap = new ConcurrentHashMap>(); + + public EventDispatcher() { + + executor = Executors.newSingleThreadExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r, "com.alibaba.nacos.naming.client.listener"); + thread.setDaemon(true); + + return thread; + } + }); + + executor.execute(new Notifier()); + } + + public void addListener(Domain dom, String clusters, EventListener listener) { + addListener(dom, clusters, StringUtils.EMPTY, listener); + } + + public void addListener(Domain dom, String clusters, String env, EventListener listener) { + List observers = Collections.synchronizedList(new ArrayList()); + observers.add(listener); + + observers = observerMap.putIfAbsent(Domain.getKey(dom.getName(), clusters, env), observers); + if (observers != null) { + observers.add(listener); + } + + domChanged(dom); + } + + public void removeListener(String dom, String clusters, EventListener listener) { + String unit = ""; + + List observers = observerMap.get(Domain.getKey(dom, clusters, unit)); + if (observers != null) { + Iterator iter = observers.iterator(); + while (iter.hasNext()) { + EventListener oldListener = iter.next(); + if (oldListener.equals(listener)) { + iter.remove(); + } + } + } + } + + public void domChanged(Domain dom) { + if (dom == null) { + return; + } + + changedDoms.add(dom); + } + + private class Notifier implements Runnable { + @Override + public void run() { + while (true) { + Domain dom = null; + try { + dom = changedDoms.poll(5, TimeUnit.MINUTES); + } catch (Exception ignore) { + } + + if (dom == null) { + continue; + } + + try { + List listeners = observerMap.get(dom.getKey()); + + if (!CollectionUtils.isEmpty(listeners)) { + for (EventListener listener : listeners) { + List hosts = Collections.unmodifiableList(dom.getHosts()); + if (!CollectionUtils.isEmpty(hosts)) { + listener.onEvent(new NamingEvent(dom.getName(), hosts)); + } + } + } + + } catch (Exception e) { + LogUtils.LOG.error("NA", "notify error for dom: " + + dom.getName() + ", clusters: " + dom.getClusters(), e); + } + } + } + } + + public void setExecutor(ExecutorService executor) { + ExecutorService oldExecutor = this.executor; + this.executor = executor; + + oldExecutor.shutdown(); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/core/HostReactor.java b/client/src/main/java/com/alibaba/nacos/client/naming/core/HostReactor.java new file mode 100644 index 00000000000..1692b61abd2 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/core/HostReactor.java @@ -0,0 +1,432 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.naming.core; + +import com.alibaba.fastjson.JSON; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.client.naming.backups.FailoverReactor; +import com.alibaba.nacos.client.naming.cache.DiskCache; +import com.alibaba.nacos.client.naming.net.NamingProxy; +import com.alibaba.nacos.client.naming.utils.LogUtils; +import com.alibaba.nacos.client.naming.utils.NetUtils; +import com.alibaba.nacos.client.naming.utils.StringUtils; +import com.alibaba.nacos.client.naming.utils.UtilAndComs; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.util.*; +import java.util.concurrent.*; + +/** + * @author xuanyin + */ +public class HostReactor { + + public static final long DEFAULT_DELAY = 1000L; + + public long updateHoldInterval = 5000L; + + private final Map> futureMap = new HashMap>(); + + private Map domMap; + + private PushRecver pushRecver; + + private EventDispatcher eventDispatcher; + + private NamingProxy serverProxy; + + private FailoverReactor failoverReactor; + + private String cacheDir; + + public HostReactor(EventDispatcher eventDispatcher, NamingProxy serverProxy, String cacheDir) { + this.eventDispatcher = eventDispatcher; + this.serverProxy = serverProxy; + this.cacheDir = cacheDir; + this.domMap = new ConcurrentHashMap<>(DiskCache.read(this.cacheDir)); + this.failoverReactor = new FailoverReactor(this, cacheDir); + this.pushRecver = new PushRecver(this); + } + + private ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r, "com.vipserver.client.updater"); + thread.setDaemon(true); + + return thread; + } + }); + + public Map getDomMap() { + return domMap; + } + + public synchronized ScheduledFuture addTask(UpdateTask task) { + return executor.schedule(task, DEFAULT_DELAY, TimeUnit.MILLISECONDS); + } + + public Domain processDomJSON(String json) { + Domain domObj = JSON.parseObject(json, Domain.class); + Domain oldDom = domMap.get(domObj.getKey()); + if (domObj.getHosts() == null || !domObj.validate()) { + //empty or error push, just ignore + return oldDom; + } + + if (oldDom != null) { + if (oldDom.getLastRefTime() > domObj.getLastRefTime()) { + LogUtils.LOG.warn("out of date data received, old-t: " + oldDom.getLastRefTime() + + ", new-t: " + domObj.getLastRefTime()); + } + + domMap.put(domObj.getKey(), domObj); + + Map oldHostMap = new HashMap(oldDom.getHosts().size()); + for (Instance host : oldDom.getHosts()) { + oldHostMap.put(host.toInetAddr(), host); + } + + Map newHostMap = new HashMap(domObj.getHosts().size()); + for (Instance host : domObj.getHosts()) { + newHostMap.put(host.toInetAddr(), host); + } + + Set modHosts = new HashSet(); + Set newHosts = new HashSet(); + Set remvHosts = new HashSet(); + + List> newDomHosts = new ArrayList>(newHostMap.entrySet()); + for (Map.Entry entry : newDomHosts) { + Instance host = entry.getValue(); + String key = entry.getKey(); + if (oldHostMap.containsKey(key) && !StringUtils.equals(host.toString(), oldHostMap.get(key).toString())) { + modHosts.add(host); + continue; + } + + if (!oldHostMap.containsKey(key)) { + newHosts.add(host); + continue; + } + + } + + for (Map.Entry entry : oldHostMap.entrySet()) { + Instance host = entry.getValue(); + String key = entry.getKey(); + if (newHostMap.containsKey(key)) { + continue; + } + + if (!newHostMap.containsKey(key)) { + remvHosts.add(host); + continue; + } + + } + + if (newHosts.size() > 0) { + LogUtils.LOG.info("new ips(" + newHosts.size() + ") dom: " + + domObj.getName() + " -> " + JSON.toJSONString(newHosts)); + } + + if (remvHosts.size() > 0) { + LogUtils.LOG.info("removed ips(" + remvHosts.size() + ") dom: " + + domObj.getName() + " -> " + JSON.toJSONString(remvHosts)); + } + + if (modHosts.size() > 0) { + LogUtils.LOG.info("modified ips(" + modHosts.size() + ") dom: " + + domObj.getName() + " -> " + JSON.toJSONString(modHosts)); + } + + + domObj.setJsonFromServer(json); + + if (newHosts.size() > 0 || remvHosts.size() > 0 || modHosts.size() > 0) { + eventDispatcher.domChanged(domObj); + DiskCache.write(domObj, cacheDir); + } + + } else { + LogUtils.LOG.info("new ips(" + domObj.ipCount() + ") dom: " + domObj.getName() + " -> " + JSON.toJSONString(domObj.getHosts())); + domMap.put(domObj.getKey(), domObj); + eventDispatcher.domChanged(domObj); + domObj.setJsonFromServer(json); + DiskCache.write(domObj, cacheDir); + } + + LogUtils.LOG.info("current ips:(" + domObj.ipCount() + ") dom: " + domObj.getName() + + " -> " + JSON.toJSONString(domObj.getHosts())); + + return domObj; + } + + private Domain getDom0(String dom, String clusters, String env) { + + String key = Domain.getKey(dom, clusters, env, false); + + return domMap.get(key); + } + + private Domain getDom0(String dom, String clusters, String env, boolean allIPs) { + + String key = Domain.getKey(dom, clusters, env, allIPs); + return domMap.get(key); + } + + public Domain getDom(String dom, String clusters, String env) { + return getDom(dom, clusters, env, false); + } + + public Domain getDom(String dom, String clusters) { + String env = StringUtils.EMPTY; + return getDom(dom, clusters, env, false); + } + + public Domain getDom(final String dom, final String clusters, final String env, final boolean allIPs) { + + LogUtils.LOG.debug("failover-mode: " + failoverReactor.isFailoverSwitch()); + String key = Domain.getKey(dom, clusters, env, allIPs); + if (failoverReactor.isFailoverSwitch()) { + return failoverReactor.getDom(key); + } + + Domain domObj = getDom0(dom, clusters, env, allIPs); + + if (null == domObj) { + domObj = new Domain(dom, clusters, env); + + if (allIPs) { + domObj.setAllIPs(allIPs); + } + + domMap.put(domObj.getKey(), domObj); + + if (allIPs) { + updateDom4AllIPNow(dom, clusters, env); + } else { + updateDomNow(dom, clusters, env); + } + } else if (domObj.getHosts().isEmpty()) { + + if (updateHoldInterval > 0) { + // hold a moment waiting for update finish + synchronized (domObj) { + try { + domObj.wait(updateHoldInterval); + } catch (InterruptedException e) { + LogUtils.LOG.error("[getDom]", "dom:" + dom + ", clusters:" + clusters + ", allIPs:" + allIPs, e); + } + } + } + } + + scheduleUpdateIfAbsent(dom, clusters, env, allIPs); + + return domMap.get(domObj.getKey()); + } + + public void scheduleUpdateIfAbsent(String dom, String clusters, String env, boolean allIPs) { + if (futureMap.get(Domain.getKey(dom, clusters, env, allIPs)) != null) { + return; + } + + synchronized (futureMap) { + if (futureMap.get(Domain.getKey(dom, clusters, env, allIPs)) != null) { + return; + } + + ScheduledFuture future = addTask(new UpdateTask(dom, clusters, env, allIPs)); + futureMap.put(Domain.getKey(dom, clusters, env, allIPs), future); + } + } + + public void updateDom4AllIPNow(String dom, String clusters, String env) { + updateDom4AllIPNow(dom, clusters, env, -1L); + } + + @SuppressFBWarnings("NN_NAKED_NOTIFY") + public void updateDom4AllIPNow(String dom, String clusters, String env, long timeout) { + try { + Map params = new HashMap(8); + params.put("dom", dom); + params.put("clusters", clusters); + params.put("udpPort", String.valueOf(pushRecver.getUDPPort())); + + Domain oldDom = getDom0(dom, clusters, env, true); + if (oldDom != null) { + params.put("checksum", oldDom.getChecksum()); + } + + String result = serverProxy.reqAPI(UtilAndComs.NACOS_URL_BASE + "/api/srvAllIP", params); + if (StringUtils.isNotEmpty(result)) { + Domain domain = processDomJSON(result); + domain.setAllIPs(true); + } + + if (oldDom != null) { + synchronized (oldDom) { + oldDom.notifyAll(); + } + } + + //else nothing has changed + } catch (Exception e) { + LogUtils.LOG.error("NA", "failed to update dom: " + dom, e); + } + } + + @SuppressFBWarnings("NN_NAKED_NOTIFY") + public void updateDomNow(String dom, String clusters, String env) { + Domain oldDom = getDom0(dom, clusters, env); + try { + Map params = new HashMap(8); + params.put("dom", dom); + params.put("clusters", clusters); + params.put("udpPort", String.valueOf(pushRecver.getUDPPort())); + params.put("env", env); + params.put("clientIP", NetUtils.localIP()); + + StringBuilder stringBuilder = new StringBuilder(); + for (String string : Balancer.UNCONSISTENT_DOM_WITH_ADDRESS_SERVER) { + stringBuilder.append(string).append(","); + } + + Balancer.UNCONSISTENT_DOM_WITH_ADDRESS_SERVER.clear(); + params.put("unconsistentDom", stringBuilder.toString()); + + String envSpliter = ","; + if (!StringUtils.isEmpty(env) && !env.contains(envSpliter)) { + params.put("useEnvId", "true"); + } + + if (oldDom != null) { + params.put("checksum", oldDom.getChecksum()); + } + + String result = serverProxy.reqAPI(UtilAndComs.NACOS_URL_BASE + "/api/srvIPXT", params); + if (StringUtils.isNotEmpty(result)) { + processDomJSON(result); + } + //else nothing has changed + } catch (Exception e) { + LogUtils.LOG.error("NA", "failed to update dom: " + dom, e); + } finally { + if (oldDom != null) { + synchronized (oldDom) { + oldDom.notifyAll(); + } + } + } + } + + public void refreshOnly(String dom, String clusters, String env, boolean allIPs) { + try { + Map params = new HashMap(16); + params.put("dom", dom); + params.put("clusters", clusters); + params.put("udpPort", String.valueOf(pushRecver.getUDPPort())); + params.put("unit", env); + params.put("clientIP", NetUtils.localIP()); + + String domSpliter = ","; + StringBuilder stringBuilder = new StringBuilder(); + for (String string : Balancer.UNCONSISTENT_DOM_WITH_ADDRESS_SERVER) { + stringBuilder.append(string).append(domSpliter); + } + + Balancer.UNCONSISTENT_DOM_WITH_ADDRESS_SERVER.clear(); + params.put("unconsistentDom", stringBuilder.toString()); + + String envSpliter = ","; + if (!env.contains(envSpliter)) { + params.put("useEnvId", "true"); + } + + if (allIPs) { + serverProxy.reqAPI(UtilAndComs.NACOS_URL_BASE + "/api/srvAllIP", params); + } else { + serverProxy.reqAPI(UtilAndComs.NACOS_URL_BASE + "/api/srvIPXT", params); + } + } catch (Exception e) { + LogUtils.LOG.error("NA", "failed to update dom: " + dom, e); + } + } + + + public class UpdateTask implements Runnable { + long lastRefTime = Long.MAX_VALUE; + private String clusters; + private String dom; + private String env; + private boolean allIPs = false; + + public UpdateTask(String dom, String clusters, String env) { + this.dom = dom; + this.clusters = clusters; + this.env = env; + } + + public UpdateTask(String dom, String clusters, String env, boolean allIPs) { + this.dom = dom; + this.clusters = clusters; + this.env = env; + this.allIPs = allIPs; + } + + @Override + public void run() { + try { + Domain domObj = domMap.get(Domain.getKey(dom, clusters, env, allIPs)); + + if (domObj == null) { + if (allIPs) { + updateDom4AllIPNow(dom, clusters, env); + } else { + updateDomNow(dom, clusters, env); + executor.schedule(this, DEFAULT_DELAY, TimeUnit.MILLISECONDS); + } + return; + } + + if (domObj.getLastRefTime() <= lastRefTime) { + if (allIPs) { + updateDom4AllIPNow(dom, clusters, env); + domObj = domMap.get(Domain.getKey(dom, clusters, env, true)); + } else { + updateDomNow(dom, clusters, env); + domObj = domMap.get(Domain.getKey(dom, clusters, env)); + } + + } else { + // if dom already updated by push, we should not override it + // since the push data may be different from pull through force push + refreshOnly(dom, clusters, env, allIPs); + } + + executor.schedule(this, domObj.getCacheMillis(), TimeUnit.MILLISECONDS); + + lastRefTime = domObj.getLastRefTime(); + } catch (Throwable e) { + LogUtils.LOG.warn("NA", "failed to update dom: " + dom, e); + } + + } + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/core/ProtectMode.java b/client/src/main/java/com/alibaba/nacos/client/naming/core/ProtectMode.java new file mode 100644 index 00000000000..5125c9cc037 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/core/ProtectMode.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.naming.core; + +/** + * @author dungu.zpf + */ +public class ProtectMode { + + private float protectThreshold; + + public ProtectMode() { + this.protectThreshold = 0.8F; + } + + public float getProtectThreshold() { + return protectThreshold; + } + + public void setProtectThreshold(float protectThreshold) { + this.protectThreshold = protectThreshold; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/core/PushRecver.java b/client/src/main/java/com/alibaba/nacos/client/naming/core/PushRecver.java new file mode 100644 index 00000000000..352fd2b9133 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/core/PushRecver.java @@ -0,0 +1,119 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.naming.core; + +import com.alibaba.fastjson.JSON; +import com.alibaba.nacos.client.naming.utils.IoUtils; +import com.alibaba.nacos.client.naming.utils.LogUtils; +import com.alibaba.nacos.client.naming.utils.StringUtils; + +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.nio.charset.Charset; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; + +/** + * @author xuanyin + */ +public class PushRecver implements Runnable { + + private ScheduledExecutorService executorService; + + public static final int UDP_MSS = 64 * 1024; + + private DatagramSocket udpSocket; + + private HostReactor hostReactor; + + public PushRecver(HostReactor hostReactor) { + try { + this.hostReactor = hostReactor; + udpSocket = new DatagramSocket(); + + executorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r); + thread.setDaemon(true); + thread.setName("com.alibaba.nacos.naming.push.receiver"); + return thread; + } + }); + + executorService.execute(this); + } catch (Exception e) { + LogUtils.LOG.error("NA", "init udp socket failed", e); + } + } + + @Override + public void run() { + while (true) { + try { + // byte[] is initialized with 0 full filled by default + byte[] buffer = new byte[UDP_MSS]; + DatagramPacket packet = new DatagramPacket(buffer, buffer.length); + + udpSocket.receive(packet); + + String json = new String(IoUtils.tryDecompress(packet.getData()), "UTF-8").trim(); + LogUtils.LOG.info("received push data: " + json + " from " + packet.getAddress().toString()); + + PushPacket pushPacket = JSON.parseObject(json, PushPacket.class); + String ack; + if ("dom".equals(pushPacket.type)) { + // dom update + hostReactor.processDomJSON(pushPacket.data); + + // send ack to server + ack = "{\"type\": \"push-ack\"" + + ", \"lastRefTime\":\"" + pushPacket.lastRefTime + + "\", \"data\":" + "\"\"}"; + } else if ("dump".equals(pushPacket.type)) { + // dump data to server + ack = "{\"type\": \"dump-ack\"" + + ", \"lastRefTime\": \"" + pushPacket.lastRefTime + + "\", \"data\":" + "\"" + + StringUtils.escapeJavaScript(JSON.toJSONString(hostReactor.getDomMap())) + + "\"}"; + } else { + // do nothing send ack only + ack = "{\"type\": \"unknown-ack\"" + + ", \"lastRefTime\":\"" + pushPacket.lastRefTime + + "\", \"data\":" + "\"\"}"; + } + + udpSocket.send(new DatagramPacket(ack.getBytes(Charset.forName("UTF-8")), + ack.getBytes(Charset.forName("UTF-8")).length, packet.getSocketAddress())); + } catch (Exception e) { + LogUtils.LOG.error("NA", "error while receiving push data", e); + } + } + } + + public static class PushPacket { + public String type; + public long lastRefTime; + public String data; + } + + public int getUDPPort() { + return udpSocket.getLocalPort(); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/net/HttpClient.java b/client/src/main/java/com/alibaba/nacos/client/naming/net/HttpClient.java new file mode 100644 index 00000000000..b3030393edd --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/net/HttpClient.java @@ -0,0 +1,199 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.naming.net; + +import com.alibaba.nacos.client.naming.utils.IoUtils; +import com.alibaba.nacos.client.naming.utils.LogUtils; +import com.alibaba.nacos.client.naming.utils.StringUtils; +import com.google.common.net.HttpHeaders; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.URL; +import java.net.URLEncoder; +import java.util.*; +import java.util.zip.GZIPInputStream; + +/** + * @author dungu.zpf + */ +public class HttpClient { + + public static final int TIME_OUT_MILLIS = Integer.parseInt(System.getProperty("com.taobao.vipserver.ctimeout", "50000")); + public static final int CON_TIME_OUT_MILLIS = Integer.parseInt(System.getProperty("com.taobao.vipserver.ctimeout", "3000")); + private static final boolean ENABLE_HTTPS = Boolean.parseBoolean(System.getProperty("tls.enable", "false")); + + static { + // limit max redirection + System.setProperty("http.maxRedirects", "5"); + } + + public static String getPrefix() { + if (ENABLE_HTTPS) { + return "https://"; + } + + return "http://"; + + } + + public static HttpResult httpGet(String url, List headers, Map paramValues, String encoding) { + return request(url, headers, paramValues, encoding, "GET"); + } + + public static HttpResult request(String url, List headers, Map paramValues, String encoding, String method) { + HttpURLConnection conn = null; + try { + String encodedContent = encodingParams(paramValues, encoding); + url += (null == encodedContent) ? "" : ("?" + encodedContent); + + conn = (HttpURLConnection) new URL(url).openConnection(); + + conn.setConnectTimeout(CON_TIME_OUT_MILLIS); + conn.setReadTimeout(TIME_OUT_MILLIS); + conn.setRequestMethod(method); + setHeaders(conn, headers, encoding); + conn.connect(); + LogUtils.LOG.info("Request from server: " + url); + return getResult(conn); + } catch (Exception e) { + try { + if (conn != null) { + LogUtils.LOG.warn("failed to request " + conn.getURL() + " from " + + InetAddress.getByName(conn.getURL().getHost()).getHostAddress()); + } + } catch (Exception e1) { + LogUtils.LOG.error("NA", "failed to request ", e1); + //ignore + } + + LogUtils.LOG.error("NA", "failed to request ", e); + + return new HttpResult(500, e.toString(), Collections.emptyMap()); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + } + + private static HttpResult getResult(HttpURLConnection conn) throws IOException { + int respCode = conn.getResponseCode(); + + InputStream inputStream; + if (HttpURLConnection.HTTP_OK == respCode + || HttpURLConnection.HTTP_NOT_MODIFIED == respCode) { + inputStream = conn.getInputStream(); + } else { + inputStream = conn.getErrorStream(); + } + + Map respHeaders = new HashMap(conn.getHeaderFields().size()); + for (Map.Entry> entry : conn.getHeaderFields().entrySet()) { + respHeaders.put(entry.getKey(), entry.getValue().get(0)); + } + + String encodingGzip = "gzip"; + + if (encodingGzip.equals(respHeaders.get(HttpHeaders.CONTENT_ENCODING))) { + inputStream = new GZIPInputStream(inputStream); + } + + return new HttpResult(respCode, IoUtils.toString(inputStream, getCharset(conn)), respHeaders); + } + + private static String getCharset(HttpURLConnection conn) { + String contentType = conn.getContentType(); + if (StringUtils.isEmpty(contentType)) { + return "UTF-8"; + } + + String[] values = contentType.split(";"); + if (values.length == 0) { + return "UTF-8"; + } + + String charset = "UTF-8"; + for (String value : values) { + value = value.trim(); + + if (value.toLowerCase().startsWith("charset=")) { + charset = value.substring("charset=".length()); + } + } + + return charset; + } + + private static void setHeaders(HttpURLConnection conn, List headers, String encoding) { + if (null != headers) { + for (Iterator iter = headers.iterator(); iter.hasNext(); ) { + conn.addRequestProperty(iter.next(), iter.next()); + } + } + + conn.addRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=" + + encoding); + conn.addRequestProperty("Accept-Charset", encoding); + } + + private static String encodingParams(Map params, String encoding) + throws UnsupportedEncodingException { + StringBuilder sb = new StringBuilder(); + if (null == params || params.isEmpty()) { + return null; + } + + params.put("encoding", encoding); + + for (Map.Entry entry : params.entrySet()) { + if (StringUtils.isEmpty(entry.getValue())) { + continue; + } + + sb.append(entry.getKey()).append("="); + sb.append(URLEncoder.encode(entry.getValue(), encoding)); + sb.append("&"); + } + + return sb.toString(); + } + + public static void main(String[] args) throws UnsupportedEncodingException { + Map params = new HashMap(2); + params.put("s", "Wms+rkGG8jlaBBbpl8FIDxxNQGA="); + System.out.println(encodingParams(params, "utf-8")); + } + + public static class HttpResult { + final public int code; + final public String content; + final private Map respHeaders; + + public HttpResult(int code, String content, Map respHeaders) { + this.code = code; + this.content = content; + this.respHeaders = respHeaders; + } + + public String getHeader(String name) { + return respHeaders.get(name); + } + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/net/NamingProxy.java b/client/src/main/java/com/alibaba/nacos/client/naming/net/NamingProxy.java new file mode 100644 index 00000000000..99fc72f5b3a --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/net/NamingProxy.java @@ -0,0 +1,348 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.naming.net; + +import com.alibaba.fastjson.JSON; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.client.naming.utils.*; +import com.alibaba.nacos.common.util.UuidUtil; + +import java.io.IOException; +import java.io.StringReader; +import java.net.HttpURLConnection; +import java.util.*; +import java.util.concurrent.*; + +/** + * @author dungu.zpf + */ +public class NamingProxy { + + private String namespace; + + private String endpoint; + + private String nacosDomain; + + private List serverList; + + private List serversFromEndpoint = new ArrayList<>(); + + private long lastSrvRefTime = 0L; + + private long vipSrvRefInterMillis = TimeUnit.SECONDS.toMillis(30); + + private ScheduledExecutorService executorService; + + public NamingProxy(String namespace, String endpoint, String serverList) { + + this.namespace = namespace; + this.endpoint = endpoint; + if (StringUtils.isNotEmpty(serverList)) { + this.serverList = Arrays.asList(serverList.split(",")); + if (this.serverList.size() == 1) { + this.nacosDomain = serverList; + } + } + + executorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setName("com.taobao.vipserver.serverlist.updater"); + t.setDaemon(true); + return t; + } + }); + + executorService.scheduleWithFixedDelay(new Runnable() { + @Override + public void run() { + refreshSrvIfNeed(); + } + }, 0, vipSrvRefInterMillis, TimeUnit.MILLISECONDS); + + refreshSrvIfNeed(); + } + + public List getServerListFromEndpoint() { + + try { + String urlString = "http://" + endpoint + "/vipserver/serverlist"; + + List headers = Arrays.asList("Client-Version", UtilAndComs.VERSION, + "Accept-Encoding", "gzip,deflate,sdch", + "Connection", "Keep-Alive", + "RequestId", UuidUtil.generateUuid()); + + HttpClient.HttpResult result = HttpClient.httpGet(urlString, headers, null, UtilAndComs.ENCODING); + if (HttpURLConnection.HTTP_OK != result.code) { + throw new IOException("Error while requesting: " + urlString + "'. Server returned: " + + result.code); + } + + String content = result.content; + List list = new ArrayList(); + for (String line : IoUtils.readLines(new StringReader(content))) { + if (!line.trim().isEmpty()) { + list.add(line.trim()); + } + } + + return list; + + } catch (Exception e) { + e.printStackTrace(); + } + + return null; + } + + private void refreshSrvIfNeed() { + try { + + if (!CollectionUtils.isEmpty(serverList)) { + LogUtils.LOG.info("server list provided by user: " + serverList); + return; + } + + if (System.currentTimeMillis() - lastSrvRefTime < vipSrvRefInterMillis) { + return; + } + + List list = getServerListFromEndpoint(); + + if (list.isEmpty()) { + throw new Exception("Can not acquire vipserver list"); + } + + if (!CollectionUtils.isEqualCollection(list, serversFromEndpoint)) { + LogUtils.LOG.info("SERVER-LIST", "server list is updated: " + list); + } + + serversFromEndpoint = list; + lastSrvRefTime = System.currentTimeMillis(); + } catch (Throwable e) { + LogUtils.LOG.warn("failed to update server list", e); + } + } + + public void regDom(String dom, String ip, int port, double weight, String cluster) throws NacosException { + + final Map params = new HashMap(8); + params.put("dom", dom); + params.put("ip", ip); + params.put("port", String.valueOf(port)); + params.put("weight", String.valueOf(weight)); + params.put("cluster", cluster); + + try { + doRegDom(params); + } catch (Exception e) { + try { + Thread.sleep(1000L); + doRegDom(params); + } catch (Exception e1) { + throw new NacosException(NacosException.SERVER_ERROR, e.getMessage()); + } + } + } + + public void registerService(String serviceName, Instance instance) throws NacosException { + + final Map params = new HashMap<>(8); + params.put("tenant", namespace); + params.put("ip", instance.getIp()); + params.put("port", String.valueOf(instance.getPort())); + params.put("weight", String.valueOf(instance.getWeight())); + params.put("healthy", String.valueOf(instance.isHealthy())); + params.put("metadata", JSON.toJSONString(instance.getMetadata())); + if (instance.getService() == null) { + params.put("serviceName", serviceName); + } else { + params.put("service", JSON.toJSONString(instance.getService())); + } + params.put("cluster", JSON.toJSONString(instance.getCluster())); + + reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, "PUT"); + } + + public void deregisterService(String serviceName, String ip, int port, String cluster) throws NacosException { + + final Map params = new HashMap<>(8); + params.put("tenant", namespace); + params.put("ip", ip); + params.put("port", String.valueOf(port)); + params.put("serviceName", serviceName); + params.put("cluster", cluster); + + reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, "DELETE"); + } + + public String queryList(String serviceName, String clusters, boolean healthyOnly) throws NacosException { + + final Map params = new HashMap<>(8); + params.put("tenant", namespace); + params.put("serviceName", serviceName); + params.put("clusters", clusters); + params.put("healthyOnly", String.valueOf(healthyOnly)); + + return reqAPI(UtilAndComs.NACOS_URL_BASE + "/instances", params, "GET"); + } + + private String doRegDom(Map params) throws Exception { + String api = UtilAndComs.NACOS_URL_BASE + "/api/regService"; + return reqAPI(api, params); + } + + public void deRegDom(String dom, String ip, int port, String cluster) throws NacosException { + String api = UtilAndComs.NACOS_URL_BASE + "/api/deRegService"; + + Map params = new HashMap(8); + params.put("ip", ip); + params.put("port", String.valueOf(port)); + params.put("cluster", cluster); + + params.put("dom", dom); + params.put("tenant", namespace); + + try { + reqAPI(api, params); + } catch (Exception e) { + LogUtils.LOG.error("NA", "faild to deRegDom: " + JSON.toJSONString(params), e); + } + } + + public String callAllServers(String api, Map params) throws NacosException { + String result = ""; + + List snapshot = serversFromEndpoint; + if (!CollectionUtils.isEmpty(serverList)) { + snapshot = serverList; + } + + try { + result = reqAPI(api, params, snapshot); + } catch (Exception e) { + LogUtils.LOG.error("NA", "req api:" + api + " failed, servers: " + snapshot, e); + } + + if (StringUtils.isNotEmpty(result)) { + return result; + } + + throw new IllegalStateException("failed to req API:/api/" + api + " after all sites(" + snapshot + ") tried"); + } + + public String reqAPI(String api, Map params) throws NacosException { + + + List snapshot = serversFromEndpoint; + if (!CollectionUtils.isEmpty(serverList)) { + snapshot = serverList; + } + + return reqAPI(api, params, snapshot); + } + + public String reqAPI(String api, Map params, String method) throws NacosException { + + List snapshot = serversFromEndpoint; + if (!CollectionUtils.isEmpty(serverList)) { + snapshot = serverList; + } + + return reqAPI(api, params, snapshot, method); + } + + public String callServer(String api, Map params, String curServer) throws NacosException { + return callServer(api, params, curServer, "GET"); + } + + public String callServer(String api, Map params, String curServer, String method) throws NacosException { + + List headers = Arrays.asList("Client-Version", UtilAndComs.VERSION, + "Accept-Encoding", "gzip,deflate,sdch", + "Connection", "Keep-Alive", + "RequestId", UuidUtil.generateUuid()); + + String url; + + url = HttpClient.getPrefix() + curServer + api; + + HttpClient.HttpResult result = HttpClient.request(url, headers, params, UtilAndComs.ENCODING, method); + + if (HttpURLConnection.HTTP_OK == result.code) { + return result.content; + } + + if (HttpURLConnection.HTTP_NOT_MODIFIED == result.code) { + return StringUtils.EMPTY; + } + + LogUtils.LOG.error("CALL-SERVER", "failed to req API:" + HttpClient.getPrefix() + curServer + + api + ". code:" + + result.code + " msg: " + result.content); + + throw new NacosException(NacosException.SERVER_ERROR, "failed to req API:" + HttpClient.getPrefix() + curServer + + api + ". code:" + + result.code + " msg: " + result.content); + } + + public String reqAPI(String api, Map params, List servers) { + return reqAPI(api, params, servers, "GET"); + } + + public String reqAPI(String api, Map params, List servers, String method) { + + if (CollectionUtils.isEmpty(servers) && StringUtils.isEmpty(nacosDomain)) { + throw new IllegalArgumentException("no server available"); + } + + if (servers != null && !servers.isEmpty()) { + + Random random = new Random(System.currentTimeMillis()); + int index = random.nextInt(servers.size()); + + for (int i = 0; i < servers.size(); i++) { + String server = servers.get(index); + try { + return callServer(api, params, server, method); + } catch (Exception e) { + LogUtils.LOG.error("NA", "req api:" + api + " failed, server(" + server, e); + } + + index = (index + 1) % servers.size(); + } + + throw new IllegalStateException("failed to req API:" + api + " after all servers(" + servers + ") tried"); + } + + + for (int i = 0; i < UtilAndComs.REQUEST_DOMAIN_RETRY_COUNT; i++) { + try { + return callServer(api, params, nacosDomain); + } catch (Exception e) { + LogUtils.LOG.error("NA", "req api:" + api + " failed, server(" + nacosDomain, e); + } + } + + throw new IllegalStateException("failed to req API:/api/" + api + " after all servers(" + servers + ") tried"); + + } + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/utils/Chooser.java b/client/src/main/java/com/alibaba/nacos/client/naming/utils/Chooser.java new file mode 100644 index 00000000000..3cc4a68c12e --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/utils/Chooser.java @@ -0,0 +1,229 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.naming.utils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * @author dungu.zpf + */ +public class Chooser { + + private K uniqueKey; + + private volatile Ref ref; + + public T random() { + List items = ref.items; + if (items.size() == 0) { + return null; + } + if (items.size() == 1) { + return items.get(0); + } + return items.get(ThreadLocalRandom.current().nextInt(items.size())); + } + + public T randomWithWeight() { + Ref ref = this.ref; + double random = ThreadLocalRandom.current().nextDouble(0, 1); + int index = Arrays.binarySearch(ref.weights, random); + if (index < 0) { + index = -index - 1; + } else { + return ref.items.get(index); + } + + if (index >= 0 && index < ref.weights.length) { + if (random < ref.weights[index]) { + return ref.items.get(index); + } + } + + /* This should never happen, but it ensures we will return a correct + * object in case there is some floating point inequality problem + * wrt the cumulative probabilities. */ + return ref.items.get(ref.items.size() - 1); + } + + public Chooser(K uniqueKey) { + this(uniqueKey, new ArrayList>()); + } + + public Chooser(K uniqueKey, List> pairs) { + Ref ref = new Ref(pairs); + ref.refresh(); + this.uniqueKey = uniqueKey; + this.ref = ref; + } + + + public K getUniqueKey() { + return uniqueKey; + } + + public Ref getRef() { + return ref; + } + + public void refresh(List> itemsWithWeight) { + Ref newRef = new Ref(itemsWithWeight); + newRef.refresh(); + newRef.poller = this.ref.poller.refresh(newRef.items); + this.ref = newRef; + } + + public class Ref { + private List> itemsWithWeight = new ArrayList>(); + private List items = new ArrayList(); + private Poller poller = new GenericPoller(items); + private double[] weights; + + @SuppressWarnings("unchecked") + public Ref(List> itemsWithWeight) { + this.itemsWithWeight = itemsWithWeight; + } + + public void refresh() { + Double originWeightSum = (double) 0; + + for (Pair item : itemsWithWeight) { + + double weight = item.weight(); + //ignore item which weight is zero.see test_randomWithWeight_weight0 in ChooserTest + if (!(weight > 0)) { + continue; + } + + items.add(item.item()); + if (Double.isInfinite(weight)) { + weight = 10000.0D; + } + if (Double.isNaN(weight)) { + weight = 1.0D; + } + originWeightSum += weight; + } + + double[] exactWeights = new double[items.size()]; + int index = 0; + for (Pair item : itemsWithWeight) { + double singleWeight = item.weight(); + //ignore item which weight is zero.see test_randomWithWeight_weight0 in ChooserTest + if (!(singleWeight > 0)) { + continue; + } + exactWeights[index++] = singleWeight / originWeightSum; + } + + weights = new double[items.size()]; + double randomRange = 0D; + for (int i = 0; i < index; i++) { + weights[i] = randomRange + exactWeights[i]; + randomRange += exactWeights[i]; + } + + double doublePrecisionDelta = 0.0001; + if (index != 0 && !(Math.abs(weights[index - 1] - 1) < doublePrecisionDelta)) { + throw new IllegalStateException("Cumulative Weight caculate wrong , the sum of probabilities does not equals 1."); + } + } + + @Override + public int hashCode() { + return itemsWithWeight.hashCode(); + } + + @SuppressWarnings("unchecked") + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other == null) { + return false; + } + if (getClass() != other.getClass()) { + return false; + } + if (!(other.getClass().getGenericInterfaces()[0].equals(this.getClass().getGenericInterfaces()[0]))) { + return false; + } + Ref otherRef = (Ref) other; + if (itemsWithWeight == null) { + if (otherRef.itemsWithWeight != null) { + return false; + } + } else { + if (otherRef.itemsWithWeight == null) { + return false; + } else { + return this.itemsWithWeight.equals(otherRef.itemsWithWeight); + } + } + return true; + } + } + + @Override + public int hashCode() { + return uniqueKey.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other == null) { + return false; + } + if (getClass() != other.getClass()) { + return false; + } + + Chooser otherChooser = (Chooser) other; + if (this.uniqueKey == null) { + if (otherChooser.getUniqueKey() != null) { + return false; + } + } else { + if (otherChooser.getUniqueKey() == null) { + return false; + } else if (!this.uniqueKey.equals(otherChooser.getUniqueKey())) { + return false; + } + + } + + if (this.ref == null) { + if (otherChooser.getRef() != null) { + return false; + } + } else { + if (otherChooser.getRef() == null) { + return false; + } else if (!this.ref.equals(otherChooser.getRef())) { + return false; + } + + } + + return true; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/utils/CollectionUtils.java b/client/src/main/java/com/alibaba/nacos/client/naming/utils/CollectionUtils.java new file mode 100644 index 00000000000..43801366400 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/utils/CollectionUtils.java @@ -0,0 +1,171 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.naming.utils; + +/** + * Created by harold on 2015/12/7. + */ +/* + * 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. + */ + +import java.util.*; + +/** + * Provides utility methods and decorators for {@link Collection} instances. + * + * @since Commons Collections 1.0 + * @version $Revision: 1713167 $ $Date: 2015-11-07 20:44:03 +0100 (Sat, 07 Nov 2015) $ + * + * @author Rodney Waldhoff + * @author Paul Jack + * @author Stephen Colebourne + * @author Steve Downey + * @author Herve Quiroz + * @author Peter KoBek + * @author Matthew Hawthorne + * @author Janek Bogucki + * @author Phil Steitz + * @author Steven Melzer + * @author Jon Schewe + * @author Neil O'Toole + * @author Stephen Smith + */ +public class CollectionUtils { + + /** Constant to avoid repeated object creation */ + private static Integer INTEGER_ONE = 1; + + /** + * CollectionUtils should not normally be instantiated. + */ + public CollectionUtils() { + } + + /** + * Returns a new {@link Collection} containing a - b. + * The cardinality of each element e in the returned {@link Collection} + * will be the cardinality of e in a minus the cardinality + * of e in b, or zero, whichever is greater. + * + * @param a the collection to subtract from, must not be null + * @param b the collection to subtract, must not be null + * @return a new collection with the results + * @see Collection#removeAll + */ + public static Collection subtract(final Collection a, final Collection b) { + ArrayList list = new ArrayList( a ); + for (Iterator it = b.iterator(); it.hasNext();) { + list.remove(it.next()); + } + return list; + } + + /** + * Returns a {@link Map} mapping each unique element in the given + * {@link Collection} to an {@link Integer} representing the number + * of occurrences of that element in the {@link Collection}. + *

+ * Only those elements present in the collection will appear as + * keys in the map. + * + * @param coll the collection to get the cardinality map for, must not be null + * @return the populated cardinality map + */ + public static Map getCardinalityMap(final Collection coll) { + Map count = new HashMap(coll.size()); + for (Iterator it = coll.iterator(); it.hasNext();) { + Object obj = it.next(); + Integer c = (Integer) (count.get(obj)); + if (c == null) { + count.put(obj,INTEGER_ONE); + } else { + count.put(obj,c + 1); + } + } + return count; + } + + + /** + * Returns true iff the given {@link Collection}s contain + * exactly the same elements with exactly the same cardinalities. + *

+ * That is, iff the cardinality of e in a is + * equal to the cardinality of e in b, + * for each element e in a or b. + * + * @param a the first collection, must not be null + * @param b the second collection, must not be null + * @return true iff the collections contain the same elements with the same cardinalities. + */ + public static boolean isEqualCollection(final Collection a, final Collection b) { + if(a.size() != b.size()) { + return false; + } else { + Map mapa = getCardinalityMap(a); + Map mapb = getCardinalityMap(b); + if(mapa.size() != mapb.size()) { + return false; + } else { + Iterator it = mapa.keySet().iterator(); + while(it.hasNext()) { + Object obj = it.next(); + if(getFreq(obj,mapa) != getFreq(obj,mapb)) { + return false; + } + } + return true; + } + } + } + + //----------------------------------------------------------------------- + /** + * Null-safe check if the specified collection is empty. + *

+ * Null returns true. + * + * @param coll the collection to check, may be null + * @return true if empty or null + * @since Commons Collections 3.2 + */ + public static boolean isEmpty(Collection coll) { + return (coll == null || coll.isEmpty()); + } + + + private static final int getFreq(final Object obj, final Map freqMap) { + Integer count = (Integer) freqMap.get(obj); + if (count != null) { + return count.intValue(); + } + return 0; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/utils/GenericPoller.java b/client/src/main/java/com/alibaba/nacos/client/naming/utils/GenericPoller.java new file mode 100644 index 00000000000..b0fa0438f58 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/utils/GenericPoller.java @@ -0,0 +1,41 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.naming.utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author dungu.zpf + */ +public class GenericPoller implements Poller { + + private AtomicInteger index = new AtomicInteger(0); + private List items = new ArrayList(); + + public GenericPoller(List items){ + this.items = items; + } + + public T next() { + return items.get(Math.abs(index.getAndIncrement() % items.size())); + } + + public Poller refresh(List items) { + return new GenericPoller(items); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/utils/IoUtils.java b/client/src/main/java/com/alibaba/nacos/client/naming/utils/IoUtils.java new file mode 100644 index 00000000000..1bb980277ac --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/utils/IoUtils.java @@ -0,0 +1,188 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.naming.utils; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.io.*; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.GZIPInputStream; + +/** + * @author dungu.zpf + */ +public class IoUtils { + + static public String toString(InputStream input, String encoding) throws IOException { + return (null == encoding) ? toString(new InputStreamReader(input, "UTF-8")) + : toString(new InputStreamReader(input, encoding)); + } + + static public String toString(Reader reader) throws IOException { + CharArrayWriter sw = new CharArrayWriter(); + copy(reader, sw); + return sw.toString(); + } + + static public long copy(Reader input, Writer output) throws IOException { + char[] buffer = new char[1 << 12]; + long count = 0; + for (int n = 0; (n = input.read(buffer)) >= 0; ) { + output.write(buffer, 0, n); + count += n; + } + return count; + } + + static public long copy(InputStream input, OutputStream output) throws IOException { + byte[] buffer = new byte[1024]; + int bytesRead; + int totalBytes = 0; + while ((bytesRead = input.read(buffer)) != -1) { + output.write(buffer, 0, bytesRead); + + totalBytes += bytesRead; + } + + return totalBytes; + } + + + static public List readLines(Reader input) throws IOException { + BufferedReader reader = toBufferedReader(input); + List list = new ArrayList(); + String line = null; + for (; ; ) { + line = reader.readLine(); + if (null != line) { + list.add(line); + } else { + break; + } + } + return list; + } + + static private BufferedReader toBufferedReader(Reader reader) { + return reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader( + reader); + } + + + static public void copyFile(String source, String target) throws IOException { + File sf = new File(source); + if (!sf.exists()) { + throw new IllegalArgumentException("source file does not exist."); + } + File tf = new File(target); + if (!tf.getParentFile().mkdirs()) { + throw new RuntimeException("failed to create parent directory."); + } + if (!tf.exists() && !tf.createNewFile()) { + throw new RuntimeException("failed to create target file."); + } + + FileChannel sc = null; + FileChannel tc = null; + try { + tc = new FileOutputStream(tf).getChannel(); + sc = new FileInputStream(sf).getChannel(); + sc.transferTo(0, sc.size(), tc); + } finally { + if (null != sc) { + sc.close(); + } + if (null != tc) { + tc.close(); + } + } + } + + public static void delete(File fileOrDir) throws IOException { + if (fileOrDir == null) { + return; + } + + if (fileOrDir.isDirectory()) { + cleanDirectory(fileOrDir); + } + + if (fileOrDir.delete()) { + throw new RuntimeException("failed to delete file: " + fileOrDir.getAbsolutePath()); + } + } + + + public static void cleanDirectory(File directory) throws IOException { + if (!directory.exists()) { + String message = directory + " does not exist"; + throw new IllegalArgumentException(message); + } + + if (!directory.isDirectory()) { + String message = directory + " is not a directory"; + throw new IllegalArgumentException(message); + } + + File[] files = directory.listFiles(); + if (files == null) { + throw new IOException("Failed to list contents of " + directory); + } + + IOException exception = null; + for (File file : files) { + try { + delete(file); + } catch (IOException ioe) { + exception = ioe; + } + } + + if (null != exception) { + throw exception; + } + } + + @SuppressFBWarnings("BIT_IOR_OF_SIGNED_BYTE") + public static boolean isGzipStream(byte[] bytes) { + + int minByteArraySize = 2; + if (bytes == null || bytes.length < minByteArraySize) { + return false; + } + + return GZIPInputStream.GZIP_MAGIC == ((bytes[1] << 8 | bytes[0]) & 0xFFFF); + } + + + public static byte[] tryDecompress(byte[] raw) throws Exception { + if (!isGzipStream(raw)) { + return raw; + } + + GZIPInputStream gis + = new GZIPInputStream(new ByteArrayInputStream(raw)); + ByteArrayOutputStream out + = new ByteArrayOutputStream(); + + IoUtils.copy(gis, out); + + return out.toByteArray(); + } +} + diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/utils/JvmRandom.java b/client/src/main/java/com/alibaba/nacos/client/naming/utils/JvmRandom.java new file mode 100644 index 00000000000..71a8b578af3 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/utils/JvmRandom.java @@ -0,0 +1,246 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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. + */ +/* + * 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 com.alibaba.nacos.client.naming.utils; + +import java.util.Random; + +/** + *

JVMRandom is a wrapper that supports all possible + * Random methods via the {@link java.lang.Math#random()} method + * and its system-wide {@link Random} object.

+ *

+ * It does this to allow for a Random class in which the seed is + * shared between all members of the class - a better name would + * have been SharedSeedRandom. + *

+ * N.B. the current implementation overrides the methods + * {@link Random#nextInt(int)} and {@link Random#nextLong()} + * to produce positive numbers ranging from 0 (inclusive) + * to MAX_VALUE (exclusive). + * + * @since 2.0 + * @version $Id: JVMRandom.java 911986 2010-02-19 21:19:05Z niallp $ + * @author unknown + */ +public final class JvmRandom extends Random { + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 1L; + + private static final Random SHARED_RANDOM = new Random(); + + /** + * Ensures that only the parent constructor can call reseed. + */ + private boolean constructed = false; + + /** + * Constructs a new instance. + */ + public JvmRandom() { + this.constructed = true; + } + + /** + * Unsupported in 2.0. + * + * @param seed ignored + * @throws UnsupportedOperationException + */ + public synchronized void setSeed(long seed) { + if (this.constructed) { + throw new UnsupportedOperationException(); + } + } + + /** + * Unsupported in 2.0. + * + * @return Nothing, this method always throws an UnsupportedOperationException. + * @throws UnsupportedOperationException + */ + public synchronized double nextGaussian() { + throw new UnsupportedOperationException(); + } + + /** + * Unsupported in 2.0. + * + * @param byteArray ignored + * @throws UnsupportedOperationException + */ + public void nextBytes(byte[] byteArray) { + throw new UnsupportedOperationException(); + } + + /** + *

Returns the next pseudorandom, uniformly distributed int value + * from the Math.random() sequence.

+ * Identical to nextInt(Integer.MAX_VALUE) + *

+ * N.B. All values are >= 0. + *

+ * @return the random int + */ + public int nextInt() { + return nextInt(Integer.MAX_VALUE); + } + + /** + *

Returns a pseudorandom, uniformly distributed int value between + * 0 (inclusive) and the specified value (exclusive), from + * the Math.random() sequence.

+ * + * @param n the specified exclusive max-value + * @return the random int + * @throws IllegalArgumentException when n <= 0 + */ + public int nextInt(int n) { + return SHARED_RANDOM.nextInt(n); + } + + /** + *

Returns the next pseudorandom, uniformly distributed long value + * from the Math.random() sequence.

+ * Identical to nextLong(Long.MAX_VALUE) + *

+ * N.B. All values are >= 0. + *

+ * @return the random long + */ + public long nextLong() { + return nextLong(Long.MAX_VALUE); + } + + + /** + *

Returns a pseudorandom, uniformly distributed long value between + * 0 (inclusive) and the specified value (exclusive), from + * the Math.random() sequence.

+ * + * @param n the specified exclusive max-value + * @return the random long + * @throws IllegalArgumentException when n <= 0 + */ + public static long nextLong(long n) { + if (n <= 0) { + throw new IllegalArgumentException( + "Upper bound for nextInt must be positive" + ); + } + // Code adapted from Harmony Random#nextInt(int) + // n is power of 2 + if ((n & -n) == n) { + // dropping lower order bits improves behaviour for low values of n + return next63bits() >> 63 + - bitsRequired(n-1); + } + // Not a power of two + long val; + long bits; + // reject some values to improve distribution + do { + bits = next63bits(); + val = bits % n; + } while (bits - val + (n - 1) < 0); + return val; + } + + /** + *

Returns the next pseudorandom, uniformly distributed boolean value + * from the Math.random() sequence.

+ * + * @return the random boolean + */ + public boolean nextBoolean() { + return SHARED_RANDOM.nextBoolean(); + } + + /** + *

Returns the next pseudorandom, uniformly distributed float value + * between 0.0 and 1.0 from the Math.random() + * sequence.

+ * + * @return the random float + */ + public float nextFloat() { + return SHARED_RANDOM.nextFloat(); + } + + /** + *

Synonymous to the Math.random() call.

+ * + * @return the random double + */ + public double nextDouble() { + return SHARED_RANDOM.nextDouble(); + } + + /** + * Get the next unsigned random long + * @return unsigned random long + */ + private static long next63bits(){ + // drop the sign bit to leave 63 random bits + return SHARED_RANDOM.nextLong() & 0x7fffffffffffffffL; + } + + /** + * Count the number of bits required to represent a long number. + * + * @param num long number + * @return number of bits required + */ + private static int bitsRequired(long num){ + // Derived from Hacker's Delight, Figure 5-9 + long y=num; + int n=0; + while(true){ + // 64 = number of bits in a long + if (num < 0) { + return 64-n; + } + if (y == 0) { + return n; + } + n++; + num=num << 1; + y=y >> 1; + } + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/utils/LogUtils.java b/client/src/main/java/com/alibaba/nacos/client/naming/utils/LogUtils.java new file mode 100644 index 00000000000..7c5e6d42c9c --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/utils/LogUtils.java @@ -0,0 +1,54 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.naming.utils; + +import com.alibaba.nacos.client.logger.Level; +import com.alibaba.nacos.client.logger.Logger; +import com.alibaba.nacos.client.logger.LoggerFactory; + +/** + * @author dingjoey + */ +public class LogUtils { + + static int JM_LOG_RETAIN_COUNT = 7; + static String JM_LOG_FILE_SIZE = "10MB"; + public static final Logger LOG; + + static { + String tmp = "7"; + try { + tmp = System.getProperty("JM.LOG.RETAIN.COUNT", "7"); + JM_LOG_RETAIN_COUNT = Integer.parseInt(tmp); + } catch (NumberFormatException e) { + e.printStackTrace(); + throw e; + } + + JM_LOG_FILE_SIZE = System.getProperty("JM.LOG.FILE.SIZE", "10MB"); + + // logger init + LOG = LoggerFactory.getLogger("com.alibaba.nacos.client.naming"); + LOG.setLevel(Level.INFO); + LOG.setAdditivity(false); + LOG.activateAppenderWithSizeRolling("nacos", "naming.log", "UTF-8", JM_LOG_FILE_SIZE, JM_LOG_RETAIN_COUNT); + } + + public static Logger logger(Class clazz) { + return LoggerFactory.getLogger(clazz); + } + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/utils/NetUtils.java b/client/src/main/java/com/alibaba/nacos/client/naming/utils/NetUtils.java new file mode 100644 index 00000000000..8a07758433b --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/utils/NetUtils.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.naming.utils; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * @author xuanyin.zy + */ +public class NetUtils { + private static String LOCAL_IP; + + public static String localIP() { + try { + if (!StringUtils.isEmpty(LOCAL_IP)) { + return LOCAL_IP; + } + + String ip = System.getProperty("com.taobao.vipserver.localIP", InetAddress.getLocalHost().getHostAddress()); + + return LOCAL_IP = ip; + } catch (UnknownHostException e) { + return "resolve_failed"; + } + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/utils/Pair.java b/client/src/main/java/com/alibaba/nacos/client/naming/utils/Pair.java new file mode 100644 index 00000000000..d6616d9f329 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/utils/Pair.java @@ -0,0 +1,38 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.naming.utils; + +/** + * @author dungu.zpf + */ +public class Pair { + + private T item; + private double weight; + + public Pair(T item,double weight){ + this.item = item; + this.weight = weight; + } + + public T item(){ + return item; + } + + public double weight(){ + return weight; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/utils/Poller.java b/client/src/main/java/com/alibaba/nacos/client/naming/utils/Poller.java new file mode 100644 index 00000000000..242e553f122 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/utils/Poller.java @@ -0,0 +1,38 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.naming.utils; + +import java.util.List; + +/** + * @author dungu.zpf + */ +public interface Poller { + /** + * Get next element selected by poller + * + * @return next element + */ + T next(); + + /** + * Update items stored in poller + * + * @param items new item list + * @return new poller instance + */ + Poller refresh(List items); +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/utils/RandomUtils.java b/client/src/main/java/com/alibaba/nacos/client/naming/utils/RandomUtils.java new file mode 100644 index 00000000000..44322574961 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/utils/RandomUtils.java @@ -0,0 +1,194 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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. + */ +/* + * 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 com.alibaba.nacos.client.naming.utils; + +import java.util.Random; + +/** + *

RandomUtils is a wrapper that supports all possible + * {@link java.util.Random} methods via the {@link java.lang.Math#random()} + * method and its system-wide Random object. + * + * @author Gary D. Gregory + * @since 2.0 + * @version $Id: RandomUtils.java 906320 2010-02-04 01:41:10Z sebb $ + */ +public class RandomUtils { + + /** + * An instance of {@link JvmRandom}. + */ + public static final Random JVM_RANDOM = new JvmRandom(); + +// should be possible for JVM_RANDOM? +// public static void nextBytes(byte[]) { +// public synchronized double nextGaussian(); +// } + + /** + *

Returns the next pseudorandom, uniformly distributed int value + * from the Math.random() sequence.

+ * N.B. All values are >= 0. + * @return the random int + */ + public static int nextInt() { + return nextInt(JVM_RANDOM); + } + + /** + *

Returns the next pseudorandom, uniformly distributed int value + * from the given random sequence.

+ * + * @param random the Random sequence generator. + * @return the random int + */ + public static int nextInt(Random random) { + return random.nextInt(); + } + + /** + *

Returns a pseudorandom, uniformly distributed int value + * between 0 (inclusive) and the specified value + * (exclusive), from the Math.random() sequence.

+ * + * @param n the specified exclusive max-value + * @return the random int + */ + public static int nextInt(int n) { + return nextInt(JVM_RANDOM, n); + } + + /** + *

Returns a pseudorandom, uniformly distributed int value + * between 0 (inclusive) and the specified value + * (exclusive), from the given Random sequence.

+ * + * @param random the Random sequence generator. + * @param n the specified exclusive max-value + * @return the random int + */ + public static int nextInt(Random random, int n) { + // check this cannot return 'n' + return random.nextInt(n); + } + + /** + *

Returns the next pseudorandom, uniformly distributed long value + * from the Math.random() sequence.

+ * N.B. All values are >= 0. + * @return the random long + */ + public static long nextLong() { + return nextLong(JVM_RANDOM); + } + + /** + *

Returns the next pseudorandom, uniformly distributed long value + * from the given Random sequence.

+ * + * @param random the Random sequence generator. + * @return the random long + */ + public static long nextLong(Random random) { + return random.nextLong(); + } + + /** + *

Returns the next pseudorandom, uniformly distributed boolean value + * from the Math.random() sequence.

+ * + * @return the random boolean + */ + public static boolean nextBoolean() { + return nextBoolean(JVM_RANDOM); + } + + /** + *

Returns the next pseudorandom, uniformly distributed boolean value + * from the given random sequence.

+ * + * @param random the Random sequence generator. + * @return the random boolean + */ + public static boolean nextBoolean(Random random) { + return random.nextBoolean(); + } + + /** + *

Returns the next pseudorandom, uniformly distributed float value + * between 0.0 and 1.0 from the Math.random() + * sequence.

+ * + * @return the random float + */ + public static float nextFloat() { + return nextFloat(JVM_RANDOM); + } + + /** + *

Returns the next pseudorandom, uniformly distributed float value + * between 0.0 and 1.0 from the given Random + * sequence.

+ * + * @param random the Random sequence generator. + * @return the random float + */ + public static float nextFloat(Random random) { + return random.nextFloat(); + } + + /** + *

Returns the next pseudorandom, uniformly distributed float value + * between 0.0 and 1.0 from the Math.random() + * sequence.

+ * + * @return the random double + */ + public static double nextDouble() { + return nextDouble(JVM_RANDOM); + } + + /** + *

Returns the next pseudorandom, uniformly distributed float value + * between 0.0 and 1.0 from the given Random + * sequence.

+ * + * @param random the Random sequence generator. + * @return the random double + */ + public static double nextDouble(Random random) { + return random.nextDouble(); + } + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/utils/StringUtils.java b/client/src/main/java/com/alibaba/nacos/client/naming/utils/StringUtils.java new file mode 100644 index 00000000000..59df2cd74d4 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/utils/StringUtils.java @@ -0,0 +1,164 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.naming.utils; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.util.Collection; +import java.util.Locale; + +/** + * @author dungu.zpf + */ +public class StringUtils { + public static boolean isEmpty(String str) { + return str == null || str.length() == 0; + } + + public static final String EMPTY = ""; + + public static boolean equals(String str1, String str2) { + return str1 == null ? str2 == null : str1.equals(str2); + } + + public static String join(Collection collection, String separator) { + if (collection == null) { + return null; + } + + StringBuilder stringBuilder = new StringBuilder(); + Object[] objects = collection.toArray(); + + for (int i = 0; i < collection.size() - 1; i++) { + stringBuilder.append(objects[i].toString()).append(separator); + } + + if (collection.size() > 0) { + stringBuilder.append(objects[collection.size() - 1]); + } + + return stringBuilder.toString(); + } + + public static boolean isNotEmpty(String str) { + return !isEmpty(str); + } + + public static String escapeJavaScript(String str) { + return escapeJavaStyleString(str, true, true); + } + + private static String escapeJavaStyleString(String str, boolean escapeSingleQuotes, boolean escapeForwardSlash) { + if (str == null) { + return null; + } + try { + StringWriter writer = new StringWriter(str.length() * 2); + escapeJavaStyleString(writer, str, escapeSingleQuotes, escapeForwardSlash); + return writer.toString(); + } catch (IOException ioe) { + // this should never ever happen while writing to a StringWriter + return null; + } + } + + private static String hex(char ch) { + return Integer.toHexString(ch).toUpperCase(Locale.ENGLISH); + } + + + private static void escapeJavaStyleString(Writer out, String str, boolean escapeSingleQuote, + boolean escapeForwardSlash) throws IOException { + if (out == null) { + throw new IllegalArgumentException("The Writer must not be null"); + } + if (str == null) { + return; + } + int sz; + sz = str.length(); + for (int i = 0; i < sz; i++) { + char ch = str.charAt(i); + + // handle unicode + if (ch > 0xfff) { + out.write("\\u" + hex(ch)); + } else if (ch > 0xff) { + out.write("\\u0" + hex(ch)); + } else if (ch > 0x7f) { + out.write("\\u00" + hex(ch)); + } else if (ch < 32) { + switch (ch) { + case '\b' : + out.write('\\'); + out.write('b'); + break; + case '\n' : + out.write('\\'); + out.write('n'); + break; + case '\t' : + out.write('\\'); + out.write('t'); + break; + case '\f' : + out.write('\\'); + out.write('f'); + break; + case '\r' : + out.write('\\'); + out.write('r'); + break; + default : + if (ch > 0xf) { + out.write("\\u00" + hex(ch)); + } else { + out.write("\\u000" + hex(ch)); + } + break; + } + } else { + switch (ch) { + case '\'' : + if (escapeSingleQuote) { + out.write('\\'); + } + out.write('\''); + break; + case '"' : + out.write('\\'); + out.write('"'); + break; + case '\\' : + out.write('\\'); + out.write('\\'); + break; + case '/' : + if (escapeForwardSlash) { + out.write('\\'); + } + out.write('/'); + break; + default : + out.write(ch); + break; + } + } + } + } + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/utils/ThreadLocalRandom.java b/client/src/main/java/com/alibaba/nacos/client/naming/utils/ThreadLocalRandom.java new file mode 100644 index 00000000000..ee41f3ee742 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/utils/ThreadLocalRandom.java @@ -0,0 +1,288 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.naming.utils; + +import java.security.SecureRandom; +import java.util.Random; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +/** + * A random number generator isolated to the current thread. Like the global {@link java.util.Random} generator used by + * the {@link java.lang.Math} class, a {@code ThreadLocalRandom} is initialized with an internally generated seed that + * may not otherwise be modified. When applicable, use of {@code ThreadLocalRandom} rather than shared {@code Random} + * objects in concurrent programs will typically encounter much less overhead and contention. Use of + * {@code ThreadLocalRandom} is particularly appropriate when multiple tasks (for example, each a + * {@link io.netty.util.internal.chmv8.ForkJoinTask}) use random numbers in parallel in thread pools. + * + *

+ * Usages of this class should typically be of the form: {@code ThreadLocalRandom.current().nextX(...)} (where {@code X} + * is {@code Int}, {@code Long}, etc). When all usages are of this form, it is never possible to accidently share a + * {@code ThreadLocalRandom} across multiple threads. + * + *

+ * This class also provides additional commonly used bounded random generation methods. + * + * //since 1.7 //author Doug Lea + */ +@SuppressWarnings("all") +public class ThreadLocalRandom extends Random { + + private static final AtomicLong seedUniquifier = new AtomicLong(); + + private static volatile long initialSeedUniquifier; + + public static void setInitialSeedUniquifier(long initialSeedUniquifier) { + ThreadLocalRandom.initialSeedUniquifier = initialSeedUniquifier; + } + + public static synchronized long getInitialSeedUniquifier() { + // Use the value set via the setter. + long initialSeedUniquifier = ThreadLocalRandom.initialSeedUniquifier; + // Otherwise, generate one. + if (initialSeedUniquifier == 0) { + // Try to generate a real random number from /dev/random. + // Get from a different thread to avoid blocking indefinitely on a machine without much entrophy. + final BlockingQueue queue = new LinkedBlockingQueue(); + Thread generatorThread = new Thread("initialSeedUniquifierGenerator") { + @Override + public void run() { + SecureRandom random = new SecureRandom(); // Get the real random seed from /dev/random + queue.add(random.nextLong()); + } + }; + generatorThread.start(); + + // Get the random seed from the thread with timeout. + final long timeoutSeconds = 3; + final long deadLine = System.nanoTime() + TimeUnit.SECONDS.toNanos(timeoutSeconds); + for (;;) { + long waitTime = deadLine - System.nanoTime(); + if (waitTime <= 0) { + break; + } + + try { + Long result = queue.poll(waitTime, TimeUnit.NANOSECONDS); + if (result != null) { + initialSeedUniquifier = result; + break; + } + } catch (InterruptedException ignore) { + // Ignore + } + } + + // Just in case the initialSeedUniquifier is zero or some other constant + initialSeedUniquifier ^= 0x3255ecdc33bae119L; // just a meaningless random number + initialSeedUniquifier ^= Long.reverse(System.nanoTime()); + + ThreadLocalRandom.initialSeedUniquifier = initialSeedUniquifier; + } + + return initialSeedUniquifier; + } + + private static long newSeed() { + for (;;) { + final long current = seedUniquifier.get(); + final long actualCurrent = current != 0 ? current : getInitialSeedUniquifier(); + + // L'Ecuyer, "Tables of Linear Congruential Generators of Different Sizes and Good Lattice Structure", 1999 + final long next = actualCurrent * 181783497276652981L; + + if (seedUniquifier.compareAndSet(current, next)) { + return next ^ System.nanoTime(); + } + } + } + + // same constants as Random, but must be redeclared because private + private static final long multiplier = 0x5DEECE66DL; + private static final long addend = 0xBL; + private static final long mask = (1L << 48) - 1; + + /** + * The random seed. We can't use super.seed. + */ + private long rnd; + + /** + * Initialization flag to permit calls to setSeed to succeed only while executing the Random constructor. We can't + * allow others since it would cause setting seed in one part of a program to unintentionally impact other usages by + * the thread. + */ + boolean initialized = false; + + // Padding to help avoid memory contention among seed updates in + // different TLRs in the common case that they are located near + // each other. + private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7; + + /** + * Constructor called only by localRandom.initialValue. + */ + ThreadLocalRandom() { + super(newSeed()); + initialized = true; + } + + /** + * The actual ThreadLocal + */ + private static final ThreadLocal localRandom = new ThreadLocal() { + protected ThreadLocalRandom initialValue() { + return new ThreadLocalRandom(); + } + }; + + /** + * Returns the current thread's {@code ThreadLocalRandom}. + * + * @return the current thread's {@code ThreadLocalRandom} + */ + public static ThreadLocalRandom current() { + return localRandom.get(); + } + + /** + * Throws {@code UnsupportedOperationException}. Setting seeds in this generator is not supported. + * + * @throws UnsupportedOperationException + * always + */ + public void setSeed(long seed) { + if (initialized) { + throw new UnsupportedOperationException(); + } + rnd = (seed ^ multiplier) & mask; + } + + protected int next(int bits) { + rnd = (rnd * multiplier + addend) & mask; + return (int) (rnd >>> (48 - bits)); + } + + /** + * Returns a pseudorandom, uniformly distributed value between the given least value (inclusive) and bound + * (exclusive). + * + * @param least + * the least value returned + * @param bound + * the upper bound (exclusive) + * @throws IllegalArgumentException + * if least greater than or equal to bound + * @return the next value + */ + public int nextInt(int least, int bound) { + if (least >= bound) { + throw new IllegalArgumentException(); + } + return nextInt(bound - least) + least; + } + + /** + * Returns a pseudorandom, uniformly distributed value between 0 (inclusive) and the specified value (exclusive). + * + * @param n + * the bound on the random number to be returned. Must be positive. + * @return the next value + * @throws IllegalArgumentException + * if n is not positive + */ + public long nextLong(long n) { + if (n <= 0) { + throw new IllegalArgumentException("n must be positive"); + } + + // Divide n by two until small enough for nextInt. On each + // iteration (at most 31 of them but usually much less), + // randomly choose both whether to include high bit in result + // (offset) and whether to continue with the lower vs upper + // half (which makes a difference only if odd). + long offset = 0; + while (n >= Integer.MAX_VALUE) { + int bits = next(2); + long half = n >>> 1; + long nextn = ((bits & 2) == 0) ? half : n - half; + if ((bits & 1) == 0) { + offset += n - nextn; + } + n = nextn; + } + return offset + nextInt((int) n); + } + + /** + * Returns a pseudorandom, uniformly distributed value between the given least value (inclusive) and bound + * (exclusive). + * + * @param least + * the least value returned + * @param bound + * the upper bound (exclusive) + * @return the next value + * @throws IllegalArgumentException + * if least greater than or equal to bound + */ + public long nextLong(long least, long bound) { + if (least >= bound) { + throw new IllegalArgumentException(); + } + return nextLong(bound - least) + least; + } + + /** + * Returns a pseudorandom, uniformly distributed {@code double} value between 0 (inclusive) and the specified value + * (exclusive). + * + * @param n + * the bound on the random number to be returned. Must be positive. + * @return the next value + * @throws IllegalArgumentException + * if n is not positive + */ + public double nextDouble(double n) { + if (n <= 0) { + throw new IllegalArgumentException("n must be positive"); + } + return nextDouble() * n; + } + + /** + * Returns a pseudorandom, uniformly distributed value between the given least value (inclusive) and bound + * (exclusive). + * + * @param least + * the least value returned + * @param bound + * the upper bound (exclusive) + * @return the next value + * @throws IllegalArgumentException + * if least greater than or equal to bound + */ + public double nextDouble(double least, double bound) { + if (least >= bound) { + throw new IllegalArgumentException(); + } + return nextDouble() * (bound - least) + least; + } + + private static final long serialVersionUID = -5851777807851030925L; +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/utils/UtilAndComs.java b/client/src/main/java/com/alibaba/nacos/client/naming/utils/UtilAndComs.java new file mode 100644 index 00000000000..60e79e9c639 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/utils/UtilAndComs.java @@ -0,0 +1,44 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.naming.utils; + +/** + * @author xuanyin.zy + */ +public class UtilAndComs { + + public static final String VERSION = "Nacos-Client-0.1"; + + public static final String ENCODING = "UTF-8"; + + public static final String ENV_LIST_KEY = "envList"; + + public static final String ALL_IPS = "000--00-ALL_IPS--00--000"; + + public static final String FAILOVER_SWITCH = "00-00---000-VIPSRV_FAILOVER_SWITCH-000---00-00"; + + public static final String NACOS_URL_BASE = "/nacos/v1/ns"; + + public static final String NACOS_URL_INSTANCE = NACOS_URL_BASE + "/instance"; + + public static final String DEFAULT_NAMESPACE_ID = "default"; + + public static final int REQUEST_DOMAIN_RETRY_COUNT = 3; + + public static final String DEFAULT_NAMING_ID = "default"; + + public static final String NACOS_NAMING_LOG_NAME = "com.alibaba.nacos.naming.log.filename"; +} diff --git a/client/src/main/java/com/alibaba/nacos/client/utils/AppNameUtils.java b/client/src/main/java/com/alibaba/nacos/client/utils/AppNameUtils.java new file mode 100644 index 00000000000..deec47c82b7 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/utils/AppNameUtils.java @@ -0,0 +1,96 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.utils; + +import java.io.File; + +/** + * appName util + * @author Nacos + * + */ +public class AppNameUtils { + + private static final String PARAM_MARKING_PROJECT = "project.name"; + private static final String PARAM_MARKING_JBOSS = "jboss.server.home.dir"; + private static final String PARAM_MARKING_JETTY = "jetty.home"; + private static final String PARAM_MARKING_TOMCAT = "catalina.base"; + + private static final String LINUX_ADMIN_HOME = "/home/admin/"; + private static final String SERVER_JBOSS = "jboss"; + private static final String SERVER_JETTY = "jetty"; + private static final String SERVER_TOMCAT = "tomcat"; + private static final String SERVER_UNKNOWN = "unknown server"; + + public static String getAppName() { + String appName = null; + + appName = getAppNameByProjectName(); + if (appName != null) { + return appName; + } + + appName = getAppNameByServerHome(); + if (appName != null) { + return appName; + } + + return "unknown"; + } + + + private static String getAppNameByProjectName() { + return System.getProperty(PARAM_MARKING_PROJECT); + } + + + private static String getAppNameByServerHome() { + String serverHome = null; + if (SERVER_JBOSS.equals(getServerType())) { + serverHome = System.getProperty(PARAM_MARKING_JBOSS); + } + else if (SERVER_JETTY.equals(getServerType())) { + serverHome = System.getProperty(PARAM_MARKING_JETTY); + } + else if (SERVER_TOMCAT.equals(getServerType())) { + serverHome = System.getProperty(PARAM_MARKING_TOMCAT); + } + + if (serverHome != null && serverHome.startsWith(LINUX_ADMIN_HOME)) { + return StringUtils.substringBetween(serverHome, LINUX_ADMIN_HOME, File.separator); + } + + return null; + } + + private static String getServerType() { + String serverType = null; + if (System.getProperty(PARAM_MARKING_JBOSS) != null) { + serverType = SERVER_JBOSS; + } + else if (System.getProperty(PARAM_MARKING_JETTY) != null) { + serverType = SERVER_JETTY; + } + else if (System.getProperty(PARAM_MARKING_TOMCAT) != null) { + serverType = SERVER_TOMCAT; + } + else { + serverType = SERVER_UNKNOWN; + } + return serverType; + } + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/utils/EnvUtil.java b/client/src/main/java/com/alibaba/nacos/client/utils/EnvUtil.java new file mode 100644 index 00000000000..51f54af3d19 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/utils/EnvUtil.java @@ -0,0 +1,115 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.utils; + +import java.util.List; +import java.util.Map; + +import com.alibaba.nacos.client.config.utils.LogUtils; +import com.alibaba.nacos.client.logger.Logger; + +/** + * env util. + * + * @author Nacos + * + */ +public class EnvUtil { + + final static public Logger log = LogUtils.logger(EnvUtil.class); + + public static void setSelfEnv(Map> headers) { + if (headers != null) { + List amorayTagTmp = headers.get(AMORY_TAG); + if (amorayTagTmp == null) { + if (selfAmorayTag != null) { + selfAmorayTag = null; + log.warn("selfAmoryTag:null"); + } + } else { + String amorayTagTmpStr = listToString(amorayTagTmp); + if (!amorayTagTmpStr.equals(selfAmorayTag)) { + selfAmorayTag = amorayTagTmpStr; + log.warn("selfAmoryTag:{}", selfAmorayTag); + } + } + + List vipserverTagTmp = headers.get(VIPSERVER_TAG); + if (vipserverTagTmp == null) { + if (selfVipserverTag != null) { + selfVipserverTag = null; + log.warn("selfVipserverTag:null"); + } + } else { + String vipserverTagTmpStr = listToString(vipserverTagTmp); + if (!vipserverTagTmpStr.equals(selfVipserverTag)) { + selfVipserverTag = vipserverTagTmpStr; + log.warn("selfVipserverTag:{}", selfVipserverTag); + } + } + List locationTagTmp = headers.get(LOCATION_TAG); + if (locationTagTmp == null) { + if (selfLocationTag != null) { + selfLocationTag = null; + log.warn("selfLocationTag:null"); + } + } else { + String locationTagTmpStr = listToString(locationTagTmp); + if (!locationTagTmpStr.equals(selfLocationTag)) { + selfLocationTag = locationTagTmpStr; + log.warn("selfLocationTag:{}", selfLocationTag); + } + } + } + } + + public static String getSelfAmorayTag() { + return selfAmorayTag; + } + + public static String getSelfVipserverTag() { + return selfVipserverTag; + } + + public static String getSelfLocationTag() { + return selfLocationTag; + } + + public static String listToString(List list) { + if (list == null) { + return null; + } + StringBuilder result = new StringBuilder(); + boolean first = true; + // 第一个前面不拼接"," + for (String string : list) { + if (first) { + first = false; + } else { + result.append(","); + } + result.append(string); + } + return result.toString(); + } + + private static String selfAmorayTag; + private static String selfVipserverTag; + private static String selfLocationTag; + public final static String AMORY_TAG = "Amory-Tag"; + public final static String VIPSERVER_TAG = "Vipserver-Tag"; + public final static String LOCATION_TAG = "Location-Tag"; +} diff --git a/client/src/main/java/com/alibaba/nacos/client/utils/IPUtil.java b/client/src/main/java/com/alibaba/nacos/client/utils/IPUtil.java new file mode 100644 index 00000000000..39ff2d49610 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/utils/IPUtil.java @@ -0,0 +1,55 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.utils; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +/** + * ip tool + * @author Nacos + * + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public class IPUtil { + + public static boolean isIPV4(String addr) { + if (null == addr) { + return false; + } + String rexp = "^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)$"; + + Pattern pat = Pattern.compile(rexp); + + Matcher mat = pat.matcher(addr); + + boolean ipAddress = mat.find(); + return ipAddress; + } + + public static boolean isIPV6(String addr) { + if (null == addr) { + return false; + } + String rexp = "^([\\da-fA-F]{1,4}:){7}[\\da-fA-F]{1,4}$"; + + Pattern pat = Pattern.compile(rexp); + + Matcher mat = pat.matcher(addr); + + boolean ipAddress = mat.find(); + return ipAddress; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/utils/JSONUtils.java b/client/src/main/java/com/alibaba/nacos/client/utils/JSONUtils.java new file mode 100644 index 00000000000..6b5092a389d --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/utils/JSONUtils.java @@ -0,0 +1,60 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.utils; + +import java.io.IOException; + +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.DeserializationConfig.Feature; +import org.codehaus.jackson.type.JavaType; +import org.codehaus.jackson.type.TypeReference; + +/** + * Json tool + * @author Nacos + * + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public class JSONUtils { + + static ObjectMapper mapper = new ObjectMapper(); + + static { + mapper.disable(Feature.FAIL_ON_UNKNOWN_PROPERTIES); + } + + public static String serializeObject(Object o) throws IOException { + return mapper.writeValueAsString(o); + } + + public static Object deserializeObject(String s, Class clazz) throws IOException { + return mapper.readValue(s, clazz); + } + + public static Object deserializeObject(String s, TypeReference typeReference) + throws IOException { + return mapper.readValue(s, typeReference); + } + + public static JavaType getCollectionType(Class collectionClass, Class... elementClasses) { + return mapper.getTypeFactory().constructParametricType(collectionClass, elementClasses); + } + + public static Object deserializeCollection(String s, JavaType type) throws IOException { + return mapper.readValue(s, type); + } + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/utils/ParamUtil.java b/client/src/main/java/com/alibaba/nacos/client/utils/ParamUtil.java new file mode 100644 index 00000000000..ccaf99bc175 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/utils/ParamUtil.java @@ -0,0 +1,162 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.utils; + +import java.io.InputStream; +import java.util.Properties; + +import com.alibaba.nacos.client.config.impl.HttpSimpleClient; +import com.alibaba.nacos.client.config.utils.LogUtils; +import com.alibaba.nacos.client.config.utils.ParamUtils; +import com.alibaba.nacos.client.logger.Logger; + +/** + * manage param tool + * @author nacos + * + */ +public class ParamUtil { + final static public Logger log = LogUtils.logger(ParamUtils.class); + + private static String defaultContextPath = "nacos"; + private static String defaultNodesPath = "serverlist"; + private static String appKey; + private static String appName; + private static String defaultServerPort; + private static String clientVersion = "unknown"; + private static int connectTimeout; + private static double perTaskConfigSize = 3000; + + static { + // 客户端身份信息 + appKey = System.getProperty("nacos.client.appKey", ""); + + appName = AppNameUtils.getAppName(); + + String defaultServerPortTmp = "8080"; + + defaultServerPort = System.getProperty("nacos.server.port", defaultServerPortTmp); + log.info("settings", "[req-serv] nacos-server port:{}", defaultServerPort); + + String tmp = "1000"; + try { + tmp = System.getProperty("NACOS.CONNECT.TIMEOUT","1000"); + connectTimeout = Integer.parseInt(tmp); + } catch (NumberFormatException e) { + final String msg = "[http-client] invalid connect timeout:" + tmp; + log.error("settings", "NACOS-XXXX", msg, e); + throw new IllegalArgumentException(msg, e); + } + log.info("settings","[http-client] connect timeout:{}", connectTimeout); + + try { + InputStream in = HttpSimpleClient.class.getClassLoader() + .getResourceAsStream("application.properties"); + Properties props = new Properties(); + props.load(in); + String val = null; + val = props.getProperty("version"); + if (val != null) { + clientVersion = val; + } + log.info("NACOS_CLIENT_VERSION:{}", clientVersion); + } catch (Exception e) { + log.error("500", "read application.properties", e); + } + + try { + perTaskConfigSize = Double.valueOf(System.getProperty("PER_TASK_CONFIG_SIZE", "3000")); + log.warn("PER_TASK_CONFIG_SIZE:", perTaskConfigSize); + } catch (Throwable t) { + log.error("PER_TASK_CONFIG_SIZE", "PER_TASK_CONFIG_SIZE invalid", t); + } + } + + + public static String getAppKey() { + return appKey; + } + + + public static void setAppKey(String appKey) { + ParamUtil.appKey = appKey; + } + + + public static String getAppName() { + return appName; + } + + + public static void setAppName(String appName) { + ParamUtil.appName = appName; + } + + + public static String getDefaultContextPath() { + return defaultContextPath; + } + + + public static void setDefaultContextPath(String defaultContextPath) { + ParamUtil.defaultContextPath = defaultContextPath; + } + + + public static String getClientVersion() { + return clientVersion; + } + + + public static void setClientVersion(String clientVersion) { + ParamUtil.clientVersion = clientVersion; + } + + + public static int getConnectTimeout() { + return connectTimeout; + } + + + public static void setConnectTimeout(int connectTimeout) { + ParamUtil.connectTimeout = connectTimeout; + } + + + public static double getPerTaskConfigSize() { + return perTaskConfigSize; + } + + + public static void setPerTaskConfigSize(double perTaskConfigSize) { + ParamUtil.perTaskConfigSize = perTaskConfigSize; + } + + + public static String getDefaultServerPort() { + return defaultServerPort; + } + + public static String getDefaultNodesPath() { + return defaultNodesPath; + } + + + public static void setDefaultNodesPath(String defaultNodesPath) { + ParamUtil.defaultNodesPath = defaultNodesPath; + } + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/utils/StringUtils.java b/client/src/main/java/com/alibaba/nacos/client/utils/StringUtils.java new file mode 100644 index 00000000000..608a2f75fe1 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/utils/StringUtils.java @@ -0,0 +1,69 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client.utils; + +/** + * string util + * @author Nacos + * + */ +public class StringUtils { + + public static final int INDEX_NOT_FOUND = -1; + + public static boolean isBlank(String str) { + int strLen; + if (str == null || (strLen = str.length()) == 0) { + return true; + } + for (int i = 0; i < strLen; i++) { + if ((Character.isWhitespace(str.charAt(i)) == false)) { + return false; + } + } + return true; + } + + public static boolean isNotEmpty(String str) { + return !StringUtils.isEmpty(str); + } + + public static boolean isEmpty(String str) { + return str == null || str.length() == 0; + } + + public static String defaultIfEmpty(String str, String defaultStr) { + return StringUtils.isEmpty(str) ? defaultStr : str; + } + + public static boolean equals(String str1, String str2) { + return str1 == null ? str2 == null : str1.equals(str2); + } + + public static String substringBetween(String str, String open, String close) { + if (str == null || open == null || close == null) { + return null; + } + int start = str.indexOf(open); + if (start != INDEX_NOT_FOUND) { + int end = str.indexOf(close, start + open.length()); + if (end != INDEX_NOT_FOUND) { + return str.substring(start + open.length(), end); + } + } + return null; + } +} diff --git a/client/src/main/resources/application.properties b/client/src/main/resources/application.properties new file mode 100644 index 00000000000..e5683df88cb --- /dev/null +++ b/client/src/main/resources/application.properties @@ -0,0 +1 @@ +version=${project.version} \ No newline at end of file diff --git a/client/src/test/java/com/alibaba/nacos/client/AppTest.java b/client/src/test/java/com/alibaba/nacos/client/AppTest.java new file mode 100644 index 00000000000..7c119e06791 --- /dev/null +++ b/client/src/test/java/com/alibaba/nacos/client/AppTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.client; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Unit test for simple App. + */ +public class AppTest + extends TestCase +{ + /** + * Create the test case + * + * @param testName name of the test case + */ + public AppTest( String testName ) + { + super( testName ); + } + + /** + * @return the suite of tests being tested + */ + public static Test suite() + { + return new TestSuite( AppTest.class ); + } + + /** + * Rigourous Test :-) + */ + public void testApp() + { + assertTrue( true ); + } +} diff --git a/common/license b/common/license new file mode 100644 index 00000000000..e2ccce1fa96 --- /dev/null +++ b/common/license @@ -0,0 +1,15 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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. + */ \ No newline at end of file diff --git a/common/pom.xml b/common/pom.xml new file mode 100644 index 00000000000..48cf0b1f0f5 --- /dev/null +++ b/common/pom.xml @@ -0,0 +1,38 @@ + + + + com.alibaba.nacos + nacos-all + 0.1.0 + + 4.0.0 + + nacos-common + jar + + nacos-common ${project.version} + http://maven.apache.org + + + UTF-8 + + + + + commons-io + commons-io + + + junit + junit + test + + + + org.apache.commons + commons-lang3 + + + diff --git a/common/src/main/java/com/alibaba/nacos/common/AppendLicense.java b/common/src/main/java/com/alibaba/nacos/common/AppendLicense.java new file mode 100644 index 00000000000..ff984eda77e --- /dev/null +++ b/common/src/main/java/com/alibaba/nacos/common/AppendLicense.java @@ -0,0 +1,85 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.common; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import org.apache.commons.io.IOUtils; + +/** + * + * 遍历目标文件夹下的所有文件,在文件头上加上license协议 + * 注意:读取/写入文件默认用utf-8进行 + * @author en.xuze@alipay.com + * @version $Id: AppendLicense.java, v 0.1 2018年7月4日 下午2:31:16 en.xuze@alipay.com Exp $ + */ +public class AppendLicense { + + private static List targetFiles = new LinkedList(); + private static String licenseFile = "/Users/en.xuze/git/nacos/common/license"; + private static String targetDirOrFile = "/Users/en.xuze/git/nacos"; + + public static void main(String[] args) throws Exception { + List licenseContents = IOUtils.readLines(new FileInputStream(new File(licenseFile)), "utf-8"); + readFiles(targetDirOrFile); + for (Iterator iterator = targetFiles.iterator(); iterator.hasNext();) { + File file = (File) iterator.next(); + List srcFileContents = IOUtils.readLines(new FileInputStream(file), "utf-8"); + List writeContents = new ArrayList<>(); + writeContents.addAll(licenseContents); + writeContents.addAll(srcFileContents); + IOUtils.writeLines(writeContents, "\n", new FileOutputStream(file)); + System.out.println("append license to file:" + file.getAbsolutePath()); + } + } + + private static void readFiles(String filePath) { + if (filePath == null) { + return; + } + File temp = new File(filePath); + File[] files = null; + if (temp.isFile()) { + if (needAppend(temp.getName())) { + targetFiles.add(temp); + } + } else { + files = temp.listFiles(); + } + if (files == null) { + return; + } + for (File f : files) { + if (f.isFile()) { + if (needAppend(f.getName())) { + targetFiles.add(f); + } + } else if (f.isDirectory()) { + readFiles(f.getPath()); + } + } + } + + private static boolean needAppend(String fileName) { + return (fileName.endsWith(".java")); + } +} diff --git a/common/src/main/java/com/alibaba/nacos/common/util/IoUtils.java b/common/src/main/java/com/alibaba/nacos/common/util/IoUtils.java new file mode 100644 index 00000000000..f9f489dc005 --- /dev/null +++ b/common/src/main/java/com/alibaba/nacos/common/util/IoUtils.java @@ -0,0 +1,173 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.common.util; + +import org.apache.commons.lang3.StringUtils; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.GZIPInputStream; + +/** + * IO related tool methods + * + * @author nacos + */ +public class IoUtils { + + static public String toString(InputStream input, String encoding) throws IOException { + return (null == encoding) ? toString(new InputStreamReader(input, "UTF-8")) + : toString(new InputStreamReader(input, encoding)); + } + + static public String toString(Reader reader) throws IOException { + CharArrayWriter sw = new CharArrayWriter(); + copy(reader, sw); + return sw.toString(); + } + + + static public long copy(Reader input, Writer output) throws IOException { + char[] buffer = new char[1 << 12]; + long count = 0; + for (int n = 0; (n = input.read(buffer)) >= 0; ) { + output.write(buffer, 0, n); + count += n; + } + return count; + } + + static public long copy(InputStream input, OutputStream output) throws IOException { + byte[] buffer = new byte[1024]; + int bytesRead; + int totalBytes = 0; + while ((bytesRead = input.read(buffer)) != -1) { + output.write(buffer, 0, bytesRead); + + totalBytes += bytesRead; + } + + return totalBytes; + } + + static public List readLines(Reader input) throws IOException { + BufferedReader reader = toBufferedReader(input); + List list = new ArrayList(); + String line = null; + for (; ; ) { + line = reader.readLine(); + if (null != line) { + if (StringUtils.isNotEmpty(line)) { + list.add(line.trim()); + } + } else { + break; + } + } + return list; + } + + static private BufferedReader toBufferedReader(Reader reader) { + return reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader( + reader); + } + + + public static boolean delete(File fileOrDir) throws IOException { + if (fileOrDir == null) { + return false; + } + + if (fileOrDir.isDirectory()) { + cleanDirectory(fileOrDir); + } + + return fileOrDir.delete(); + } + + public static void cleanDirectory(File directory) throws IOException { + if (!directory.exists()) { + String message = directory + " does not exist"; + throw new IllegalArgumentException(message); + } + + if (!directory.isDirectory()) { + String message = directory + " is not a directory"; + throw new IllegalArgumentException(message); + } + + File[] files = directory.listFiles(); + if (files == null) { + throw new IOException("Failed to list contents of " + directory); + } + + IOException exception = null; + for (File file : files) { + try { + delete(file); + } catch (IOException ioe) { + exception = ioe; + } + } + + if (null != exception) { + throw exception; + } + } + + public static void writeStringToFile(File file, String data, String encoding) + throws IOException { + OutputStream os = null; + try { + os = new FileOutputStream(file); + os.write(data.getBytes(encoding)); + os.flush(); + } finally { + if (null != os) { + os.close(); + } + } + } + + public static byte[] tryDecompress(InputStream raw) throws Exception { + + try { + GZIPInputStream gis + = new GZIPInputStream(raw); + ByteArrayOutputStream out + = new ByteArrayOutputStream(); + + + IoUtils.copy(gis, out); + + return out.toByteArray(); + } catch (Exception e) { + e.printStackTrace(); + } + + return null; + } + + public static void main(String[] args) throws IOException { + +// String path = "/Users/zhupengfei/test_write.txt"; + +// writeStringToFile(new File(path), "hello2222", "utf-8"); + } + +} + diff --git a/common/src/main/java/com/alibaba/nacos/common/util/Md5Utils.java b/common/src/main/java/com/alibaba/nacos/common/util/Md5Utils.java new file mode 100644 index 00000000000..bcccaa55e41 --- /dev/null +++ b/common/src/main/java/com/alibaba/nacos/common/util/Md5Utils.java @@ -0,0 +1,58 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.common.util; + +import java.io.UnsupportedEncodingException; + +/** + * MD5 generator + * + * @author nacos + */ +public class Md5Utils { + + private static final int HEX_VALUE_COUNT = 16; + + public static String getMD5(byte[] bytes) { + char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + char str[] = new char[16 * 2]; + try { + java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5"); + md.update(bytes); + byte tmp[] = md.digest(); + int k = 0; + for (int i = 0; i < HEX_VALUE_COUNT; i++) { + byte byte0 = tmp[i]; + str[k++] = hexDigits[byte0 >>> 4 & 0xf]; + str[k++] = hexDigits[byte0 & 0xf]; + } + } catch (Exception e) { + e.printStackTrace(); + } + return new String(str); + } + + public static String getMD5(String value, String encode) { + String result = ""; + try { + result = getMD5(value.getBytes(encode)); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + + return result; + } +} diff --git a/common/src/main/java/com/alibaba/nacos/common/util/Pair.java b/common/src/main/java/com/alibaba/nacos/common/util/Pair.java new file mode 100644 index 00000000000..c6a0682709e --- /dev/null +++ b/common/src/main/java/com/alibaba/nacos/common/util/Pair.java @@ -0,0 +1,51 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.common.util; + +import org.apache.commons.lang3.StringUtils; + +/** + * @author nacos + */ +public class Pair { + private String value0; + private String value1; + + public Pair(String value0, String value1) { + this.value0 = value0; + this.value1 = value1; + } + + public Pair() { + this(StringUtils.EMPTY, StringUtils.EMPTY); + } + + public String getValue0() { + return value0; + } + + public void setValue0(String value0) { + this.value0 = value0; + } + + public String getValue1() { + return value1; + } + + public void setValue1(String value1) { + this.value1 = value1; + } +} diff --git a/common/src/main/java/com/alibaba/nacos/common/util/SystemUtil.java b/common/src/main/java/com/alibaba/nacos/common/util/SystemUtil.java new file mode 100644 index 00000000000..33d96bb10bb --- /dev/null +++ b/common/src/main/java/com/alibaba/nacos/common/util/SystemUtil.java @@ -0,0 +1,63 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.common.util; + +import java.lang.management.ManagementFactory; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.apache.commons.lang3.StringUtils; +import com.sun.management.OperatingSystemMXBean; + +/** + * @author nacos + */ +public class SystemUtil { + + private static OperatingSystemMXBean operatingSystemMXBean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); + + public static List getIPsBySystemEnv(String key) { + String env = getSystemEnv(key); + List ips = new ArrayList<>(); + if (StringUtils.isNotEmpty(env)) { + ips = Arrays.asList(env.split(",")); + } + return ips; + } + + public static String getSystemEnv(String key) { + String env = System.getenv(key); + return env; + } + + public static void main(String[] args) throws SQLException { + System.out.println(Boolean.parseBoolean("Tfue")); + } + + public static float getLoad() { + return (float) operatingSystemMXBean.getSystemLoadAverage(); + } + + public static float getCPU() { + return (float) operatingSystemMXBean.getSystemCpuLoad(); + } + + public static float getMem() { + return (float) (1 - (double) operatingSystemMXBean.getFreePhysicalMemorySize() / (double) operatingSystemMXBean.getTotalPhysicalMemorySize()); + } +} diff --git a/common/src/main/java/com/alibaba/nacos/common/util/UuidUtil.java b/common/src/main/java/com/alibaba/nacos/common/util/UuidUtil.java new file mode 100644 index 00000000000..76811e0ad4e --- /dev/null +++ b/common/src/main/java/com/alibaba/nacos/common/util/UuidUtil.java @@ -0,0 +1,13 @@ +package com.alibaba.nacos.common.util; + +import java.util.UUID; + +/** + * @author dungu.zpf + */ +public class UuidUtil { + + public static String generateUuid() { + return UUID.randomUUID().toString(); + } +} diff --git a/common/src/test/java/com/alibaba/nacos/common/AppTest.java b/common/src/test/java/com/alibaba/nacos/common/AppTest.java new file mode 100644 index 00000000000..ebb16c14294 --- /dev/null +++ b/common/src/test/java/com/alibaba/nacos/common/AppTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.common; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Unit test for simple App. + */ +public class AppTest + extends TestCase +{ + /** + * Create the test case + * + * @param testName name of the test case + */ + public AppTest( String testName ) + { + super( testName ); + } + + /** + * @return the suite of tests being tested + */ + public static Test suite() + { + return new TestSuite( AppTest.class ); + } + + /** + * Rigourous Test :-) + */ + public void testApp() + { + assertTrue( true ); + } +} diff --git a/config/pom.xml b/config/pom.xml new file mode 100644 index 00000000000..0ed2283e768 --- /dev/null +++ b/config/pom.xml @@ -0,0 +1,155 @@ + + + + com.alibaba.nacos + nacos-all + 0.1.0 + + + 4.0.0 + + nacos-config + jar + + nacos-config ${project.version} + http://maven.apache.org + + + UTF-8 + + + + + junit + junit + test + + + org.springframework.boot + spring-boot-starter-web + + + ${project.groupId} + nacos-core + + + com.google.guava + guava + + + taglibs + standard + + + org.springframework.boot + spring-boot-starter-jdbc + + + commons-io + commons-io + + + commons-lang + commons-lang + + + mysql + mysql-connector-java + + + commons-dbcp + commons-dbcp + + + org.apache.derby + derby + + + ch.qos.logback + logback-classic + + + + + org.aspectj + aspectjrt + + + cglib + cglib-nodep + + + org.apache.httpcomponents + httpasyncclient + + + + + org.springframework.boot + spring-boot-starter-tomcat + + + org.codehaus.jackson + jackson-mapper-lgpl + + + net.jcip + jcip-annotations + true + + + com.github.spotbugs + spotbugs-annotations + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + -Dnacos.standalone=true + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + com.alibaba.nacos.config.server.Config + + + + jar-with-dependencies + + + + + + + + springboot + + + com.alibaba.nacos + nacos-core + + + + + + org.springframework.boot + spring-boot-maven-plugin + + com.alibaba.nacos.config.server.Config + + + + nacos-config + + + + diff --git a/config/src/main/java/LogbackInitTest.java b/config/src/main/java/LogbackInitTest.java new file mode 100644 index 00000000000..408d0fb3a03 --- /dev/null +++ b/config/src/main/java/LogbackInitTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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. + */ +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.nacos.config.server.utils.AppNameUtils; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.joran.JoranConfigurator; + +/** + * logback test + * @author Nacos + * + */ +public class LogbackInitTest { + + private static final Logger logger = LoggerFactory.getLogger(LogbackInitTest.class); + + + + public static void main(String[] args) throws Exception { + AppNameUtils.class.getClassLoader(); + String classpath = AppNameUtils.class.getResource("/").getPath(); + System.out.println("The classpath is " + classpath); + + LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); + lc.reset(); + + JoranConfigurator configurator = new JoranConfigurator(); + configurator.setContext(lc); + configurator.doConfigure(LogbackInitTest.class.getResource("logback-jiuren.xml")); + + for (;;) { + logger.info("hello"); + System.out.println(getLevel(logger)); + Thread.sleep(1000L); + } + } + + + static String getLevel(Logger logger) { + if (logger.isDebugEnabled()) { + return "debug"; + } else if (logger.isInfoEnabled()) { + return "info"; + } else if (logger.isWarnEnabled()) { + return "warn"; + } else if (logger.isErrorEnabled()) { + return "error"; + } else { + return "unknown"; + } + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/Config.java b/config/src/main/java/com/alibaba/nacos/config/server/Config.java new file mode 100644 index 00000000000..3b2c0e0a95e --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/Config.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server; + +import java.net.UnknownHostException; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.web.servlet.ServletComponentScan; + +/** + * Config main + * + * @author Nacos + * + */ +@SpringBootApplication(scanBasePackages = "com.alibaba.nacos.config.server") +@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class}) +@ServletComponentScan +public class Config { + public static void main(String[] args) throws UnknownHostException { + SpringApplication.run(Config.class, args); + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/aspect/CapacityManagementAspect.java b/config/src/main/java/com/alibaba/nacos/config/server/aspect/CapacityManagementAspect.java new file mode 100644 index 00000000000..79376546179 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/aspect/CapacityManagementAspect.java @@ -0,0 +1,441 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.aspect; + +import java.nio.charset.Charset; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang.StringUtils; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.constant.CounterMode; +import com.alibaba.nacos.config.server.model.ConfigInfo; +import com.alibaba.nacos.config.server.model.capacity.Capacity; +import com.alibaba.nacos.config.server.service.PersistService; +import com.alibaba.nacos.config.server.service.capacity.CapacityService; +import com.alibaba.nacos.config.server.utils.PropertyUtil; + +/** + * 容量管理切面:批量写入、更新暂不处理 + * + * @author hexu.hxy + * @date 2018/3/13 + */ +@Aspect +public class CapacityManagementAspect { + private static final Logger LOGGER = LoggerFactory.getLogger(CapacityManagementAspect.class); + + private static final String SYNC_UPDATE_CONFIG_ALL + = "execution(* com.alibaba.nacos.config.server.controller.ConfigController.publishConfig(..)) && args" + + "(request,response,dataId,group,content,appName,srcUser,tenant,tag,..)"; + + private static final String DELETE_CONFIG + = "execution(* com.alibaba.nacos.config.server.controller.ConfigController.deleteConfig(..)) && args" + + "(request,response,dataId,group,tenant,..)"; + + + @Autowired + private CapacityService capacityService; + @Autowired + private PersistService persistService; + + + /** + * 更新也需要判断content内容是否超过大小限制 + */ + @Around(SYNC_UPDATE_CONFIG_ALL) + public Object aroundSyncUpdateConfigAll(ProceedingJoinPoint pjp, HttpServletRequest request, + HttpServletResponse response, String dataId, String group, String content, + String appName, String srcUser, String tenant, String tag) + throws Throwable { + if (!PropertyUtil.isManageCapacity()) { + return pjp.proceed(); + } + LOGGER.info("[capacityManagement] aroundSyncUpdateConfigAll"); + String betaIps = request.getHeader("betaIps"); + if (StringUtils.isBlank(betaIps)) { + if (StringUtils.isBlank(tag)) { + // 只对写入或更新config_info表的做容量管理的限制检验 + if (persistService.findConfigInfo(dataId, group, tenant) == null) { + // 写入操作 + return do4Insert(pjp, request, response, group, tenant, content); + } + // 更新操作 + return do4Update(pjp, request, response, dataId, group, tenant, content); + } + } + return pjp.proceed(); + } + + /** + * 更新操作:开启容量管理的限制检验功能,会检验"content的大小"是否超过限制 + * + * @throws Throwable "实际操作"抛出的异常 + */ + private Object do4Update(ProceedingJoinPoint pjp, HttpServletRequest request, HttpServletResponse response, + String dataId, String group, String tenant, String content) throws Throwable { + if (!PropertyUtil.isCapacityLimitCheck()) { + return pjp.proceed(); + } + try { + boolean hasTenant = hasTenant(tenant); + Capacity capacity = getCapacity(group, tenant, hasTenant); + if (isSizeLimited(group, tenant, getCurrentSize(content), hasTenant, false, capacity)) { + return response4Limit(request, response, LimitType.OVER_MAX_SIZE); + } + } catch (Exception e) { + LOGGER.error("[capacityManagement] do4Update ", e); + } + return pjp.proceed(); + } + + /** + * 写入操作:1. 无论是否开启容量管理的限制检验功能都会计数(usage) 2.开启容量管理的限制检验功能,会检验"限额"和"content的大小" + * + * @throws Throwable "实际操作"抛出的异常 + */ + private Object do4Insert(ProceedingJoinPoint pjp, HttpServletRequest request, + HttpServletResponse response, String group, String tenant, String content) + throws Throwable { + LOGGER.info("[capacityManagement] do4Insert"); + CounterMode counterMode = CounterMode.INCREMENT; + boolean hasTenant = hasTenant(tenant); + if (PropertyUtil.isCapacityLimitCheck()) { + // 先写入或更新:usage + 1 + LimitType limitType = getLimitType(counterMode, group, tenant, content, hasTenant); + if (limitType != null) { + return response4Limit(request, response, limitType); + } + } else { + // 先写入或更新:usage + 1 + insertOrUpdateUsage(group, tenant, counterMode, hasTenant); + } + return getResult(pjp, response, group, tenant, counterMode, hasTenant); + } + + private Object response4Limit(HttpServletRequest request, HttpServletResponse response, LimitType limitType) { + response.setStatus(limitType.status); + return String.valueOf(limitType.status); + } + + private boolean hasTenant(String tenant) { + return StringUtils.isNotBlank(tenant); + } + + /** + * 无论是否开启容量管理的限制检验功能,删除时候,计数模块中容量信息表中的usage都得减一 + */ + @Around(DELETE_CONFIG) + public Object aroundDeleteConfig(ProceedingJoinPoint pjp, HttpServletRequest request, HttpServletResponse response, + String dataId, String group, String tenant) throws Throwable { + if (!PropertyUtil.isManageCapacity()) { + return pjp.proceed(); + } + LOGGER.info("[capacityManagement] aroundDeleteConfig"); + ConfigInfo configInfo = persistService.findConfigInfo(dataId, group, tenant); + if (configInfo == null) { + return pjp.proceed(); + } + return do4Delete(pjp, response, group, tenant, configInfo); + } + + /** + * @throws Throwable "实际操作"抛出的异常 + */ + private Object do4Delete(ProceedingJoinPoint pjp, HttpServletResponse response, String group, String tenant, + ConfigInfo configInfo) + throws Throwable { + boolean hasTenant = hasTenant(tenant); + if (configInfo == null) { + // "configInfo == null"有2种可能: + // 1. 并发删除;2. 先是新增子配置,后来删除了所有子配置,这时合并写入到configInfo的task(异步)还没执行 + // 关于第2点,那么接下会顺序执行"合并写入config_info的task","删除config_info的task" + // 主动修正usage,当刚好在上述的"合并写入config_info的task"执行完时修正usage,此时usage=1 + // 而后面个"删除config_info的task"执行时并不会把usage-1,因为请求已经返回了。 + // 因此还是需要定时修正usage的Job + correctUsage(group, tenant, hasTenant); + return pjp.proceed(); + } + // 并发删除同一个记录,可能同时走到这里,加上这个接口是异步删除的(提交MergeDataTask给MergeTaskProcessor处理),可能导致usage不止减一。因此还是需要定时修正usage的Job + CounterMode counterMode = CounterMode.DECREMENT; + insertOrUpdateUsage(group, tenant, counterMode, hasTenant); + return getResult(pjp, response, group, tenant, counterMode, hasTenant); + } + + private void correctUsage(String group, String tenant, boolean hasTenant) { + try { + if (hasTenant) { + LOGGER.info("主动修正usage, tenant: {}", tenant); + capacityService.correctTenantUsage(tenant); + } else { + LOGGER.info("主动修正usage, group: {}", group); + capacityService.correctGroupUsage(group); + } + } catch (Exception e) { + LOGGER.error("[capacityManagement] correctUsage ", e); + } + } + + private Object getResult(ProceedingJoinPoint pjp, HttpServletResponse response, String group, String tenant, + CounterMode counterMode, boolean hasTenant) throws Throwable { + try { + // 执行实际操作 + Object result = pjp.proceed(); + // 根据执行结果判定是否需要回滚 + doResult(counterMode, response, group, tenant, result, hasTenant); + return result; + } catch (Throwable throwable) { + LOGGER.warn("[capacityManagement] inner operation throw exception, rollback, group: {}, tenant: {}", + group, tenant, throwable); + rollback(counterMode, group, tenant, hasTenant); + throw throwable; + } + } + + /** + * usage计数器服务:无论容量管理的限制检验功能是否开启,都会进行计数 + */ + private void insertOrUpdateUsage(String group, String tenant, CounterMode counterMode, boolean hasTenant) { + try { + capacityService.insertAndUpdateClusterUsage(counterMode, true); + if (hasTenant) { + capacityService.insertAndUpdateTenantUsage(counterMode, tenant, true); + } else { + capacityService.insertAndUpdateGroupUsage(counterMode, group, true); + } + } catch (Exception e) { + LOGGER.error("[capacityManagement] insertOrUpdateUsage ", e); + } + } + + private LimitType getLimitType(CounterMode counterMode, String group, String tenant, String content, boolean + hasTenant) { + try { + boolean clusterLimited = !capacityService.insertAndUpdateClusterUsage(counterMode, false); + if (clusterLimited) { + LOGGER.warn("[capacityManagement] cluster capacity reaches quota."); + return LimitType.OVER_CLUSTER_QUOTA; + } + if (content == null) { + return null; + } + int currentSize = getCurrentSize(content); + LimitType limitType = getGroupOrTenantLimitType(counterMode, group, tenant, currentSize, + hasTenant); + if (limitType != null) { + rollbackClusterUsage(counterMode); + return limitType; + } + } catch (Exception e) { + LOGGER.error("[capacityManagement] isLimited ", e); + } + return null; + } + + /** + * 编码字节数 + */ + private int getCurrentSize(String content) { + try { + return content.getBytes(Charset.forName(Constants.ENCODE)).length; + } catch (Exception e) { + LOGGER.error("[capacityManagement] getCurrentSize ", e); + } + return 0; + } + + private LimitType getGroupOrTenantLimitType(CounterMode counterMode, String group, String tenant, + int currentSize, boolean hasTenant) { + if (group == null) { + return null; + } + Capacity capacity = getCapacity(group, tenant, hasTenant); + if (isSizeLimited(group, tenant, currentSize, hasTenant, false, capacity)) { + return LimitType.OVER_MAX_SIZE; + } + if (capacity == null) { + insertCapacity(group, tenant, hasTenant); + } + boolean updateSuccess = isUpdateSuccess(counterMode, group, tenant, hasTenant); + if (updateSuccess) { + return null; + } + if (hasTenant) { + return LimitType.OVER_TENANT_QUOTA; + } + return LimitType.OVER_GROUP_QUOTA; + } + + private boolean isUpdateSuccess(CounterMode counterMode, String group, String tenant, boolean hasTenant) { + boolean updateSuccess; + if (hasTenant) { + updateSuccess = capacityService.updateTenantUsage(counterMode, tenant); + if (!updateSuccess) { + LOGGER.warn("[capacityManagement] tenant capacity reaches quota, tenant: {}", tenant); + } + } else { + updateSuccess = capacityService.updateGroupUsage(counterMode, group); + if (!updateSuccess) { + LOGGER.warn("[capacityManagement] group capacity reaches quota, group: {}", group); + } + } + return updateSuccess; + } + + private void insertCapacity(String group, String tenant, boolean hasTenant) { + if (hasTenant) { + capacityService.initTenantCapacity(tenant); + } else { + capacityService.initGroupCapacity(group); + } + } + + private Capacity getCapacity(String group, String tenant, boolean hasTenant) { + Capacity capacity; + if (hasTenant) { + capacity = capacityService.getTenantCapacity(tenant); + } else { + capacity = capacityService.getGroupCapacity(group); + } + return capacity; + } + + private boolean isSizeLimited(String group, String tenant, int currentSize, boolean hasTenant, boolean isAggr, + Capacity capacity) { + int defaultMaxSize = getDefaultMaxSize(isAggr); + if (capacity != null) { + Integer maxSize = getMaxSize(isAggr, capacity); + if (maxSize == 0) { + // 已经存在容量信息记录,maxSize=0,则使用"默认maxSize限制值"进行比较 + return isOverSize(group, tenant, currentSize, defaultMaxSize, hasTenant); + } + // 已经存在容量信息记录,maxSize!=0 + return isOverSize(group, tenant, currentSize, maxSize, hasTenant); + } + // 不已经存在容量信息记录,使用"默认maxSize限制值"进行比较 + return isOverSize(group, tenant, currentSize, defaultMaxSize, hasTenant); + } + + private Integer getMaxSize(boolean isAggr, Capacity capacity) { + if (isAggr) { + return capacity.getMaxAggrSize(); + } + return capacity.getMaxSize(); + } + + private int getDefaultMaxSize(boolean isAggr) { + if (isAggr) { + return PropertyUtil.getDefaultMaxAggrSize(); + } + return PropertyUtil.getDefaultMaxSize(); + } + + private boolean isOverSize(String group, String tenant, int currentSize, int maxSize, boolean hasTenant) { + if (currentSize > maxSize) { + if (hasTenant) { + LOGGER.warn( + "[capacityManagement] tenant content is over maxSize, tenant: {}, maxSize: {}, currentSize: {}", + tenant, maxSize, currentSize); + } else { + LOGGER.warn( + "[capacityManagement] group content is over maxSize, group: {}, maxSize: {}, currentSize: {}", + group, maxSize, currentSize); + } + return true; + } + return false; + } + + private void doResult(CounterMode counterMode, HttpServletResponse response, String group, + String tenant, Object result, boolean hasTenant) { + try { + if (!isSuccess(response, result)) { + LOGGER.warn( + "[capacityManagement] inner operation is fail, rollback, counterMode: {}, group: {}, tenant: {}", + counterMode, group, tenant); + rollback(counterMode, group, tenant, hasTenant); + } + } catch (Exception e) { + LOGGER.error("[capacityManagement] doResult ", e); + } + } + + private boolean isSuccess(HttpServletResponse response, Object result) { + int status = response.getStatus(); + if (status == HttpServletResponse.SC_OK) { + return true; + } + LOGGER.warn("[capacityManagement] response status is not 200, status: {}, result: {}", status, + result); + return false; + } + + private void rollback(CounterMode counterMode, String group, String tenant, boolean hasTenant) { + try { + rollbackClusterUsage(counterMode); + if (hasTenant) { + capacityService.updateTenantUsage(counterMode.reverse(), tenant); + } else { + capacityService.updateGroupUsage(counterMode.reverse(), group); + } + } catch (Exception e) { + LOGGER.error("[capacityManagement] rollback ", e); + } + } + + private void rollbackClusterUsage(CounterMode counterMode) { + try { + if (!capacityService.updateClusterUsage(counterMode.reverse())) { + LOGGER.error("[capacityManagement] cluster usage rollback fail counterMode: {}", counterMode); + } + } catch (Exception e) { + LOGGER.error("[capacityManagement] rollback ", e); + } + } + + /** + * limit tyep + * + * @author Nacos + * + */ + public enum LimitType { + /** + * over limit + */ + OVER_CLUSTER_QUOTA("超过集群配置个数上限", 429), + OVER_GROUP_QUOTA("超过该Group配置个数上限", 429), + OVER_TENANT_QUOTA("超过该租户配置个数上限", 429), + OVER_MAX_SIZE("超过配置的内容大小上限", 429), + OVER_MAX_AGGR_COUNT("超过聚合子配置个数上限", 429), + OVER_MAX_AGGR_SIZE("超过聚合数据子配置的内容大小上限", 429); + public final String description; + public final int status; + + LimitType(String description, int status) { + this.description = description; + this.status = status; + } + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/aspect/RequestLogAspect.java b/config/src/main/java/com/alibaba/nacos/config/server/aspect/RequestLogAspect.java new file mode 100755 index 00000000000..eb21966d8f8 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/aspect/RequestLogAspect.java @@ -0,0 +1,104 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.aspect; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; + +import com.alibaba.nacos.config.server.service.ConfigService; +import com.alibaba.nacos.config.server.utils.GroupKey2; +import com.alibaba.nacos.config.server.utils.LogUtil; +import com.alibaba.nacos.config.server.utils.MD5; +import com.alibaba.nacos.config.server.utils.RequestUtil; + +/** + * * Created with IntelliJ IDEA. User: dingjoey Date: 13-12-12 Time: 21:12 + * client api && sdk api 请求日志打点逻辑 + * + * @author Nacos + * + */ +@Aspect +public class RequestLogAspect { + + /** + * publish config + */ + public static final String CLIENT_INTERFACE_PUBLISH_SINGLE_CONFIG = "execution(* com.alibaba.nacos.config.server.controller.ConfigController.publishConfig(..)) && args(request,response,dataId,group,tenant,content,..)"; + + /** + * get config + */ + public static final String CLIENT_INTERFACE_GET_CONFIG = "execution(* com.alibaba.nacos.config.server.controller.ConfigController.getConfig(..)) && args(request,response,dataId,group,tenant,..)"; + + /** + * remove config + */ + public static final String CLIENT_INTERFACE_REMOVE_ALL_CONFIG = "execution(* com.alibaba.nacos.config.server.controller.ConfigController.deleteConfig(..)) && args(request,response,dataId,group,..)"; + + /** + * publishSingle + */ + @Around(CLIENT_INTERFACE_PUBLISH_SINGLE_CONFIG) + public Object interfacePublishSingle(ProceedingJoinPoint pjp, HttpServletRequest request, + HttpServletResponse response, String dataId, String group, String tenant, String content) throws Throwable { + final String md5 = content == null ? null : MD5.getInstance().getMD5String(content); + return logClientRequest("publish", pjp, request, response, dataId, group, tenant, md5); + } + + /** + * removeAll + */ + @Around(CLIENT_INTERFACE_REMOVE_ALL_CONFIG) + public Object interfaceRemoveAll(ProceedingJoinPoint pjp, HttpServletRequest request, HttpServletResponse response, + String dataId, String group, String tenant) throws Throwable { + return logClientRequest("remove", pjp, request, response, dataId, group, tenant, null); + } + + /** + * getConfig + */ + @Around(CLIENT_INTERFACE_GET_CONFIG) + public Object interfaceGetConfig(ProceedingJoinPoint pjp, HttpServletRequest request, HttpServletResponse response, + String dataId, String group, String tenant) throws Throwable { + final String groupKey = GroupKey2.getKey(dataId, group, tenant); + final String md5 = ConfigService.getContentMd5(groupKey); + return logClientRequest("get", pjp, request, response, dataId, group, tenant, md5); + } + + /** + * client api request log rt | status | requestIp | opType | dataId | group + * | datumId | md5 + */ + private Object logClientRequest(String requestType, ProceedingJoinPoint pjp, HttpServletRequest request, + HttpServletResponse response, String dataId, String group, String tenant, String md5) throws Throwable { + final String requestIp = RequestUtil.getRemoteIp(request); + String appName = request.getHeader(RequestUtil.CLIENT_APPNAME_HEADER); + final long st = System.currentTimeMillis(); + Object retVal = pjp.proceed(); + final long rt = System.currentTimeMillis() - st; + // rt | status | requestIp | opType | dataId | group | datumId | md5 | + // appName + LogUtil.clientLog.info("{}|{}|{}|{}|{}|{}|{}|{}|{}", + new Object[] { rt, retVal, requestIp, requestType, dataId, group, tenant, md5, appName }); + return retVal; + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/constant/Constants.java b/config/src/main/java/com/alibaba/nacos/config/server/constant/Constants.java new file mode 100644 index 00000000000..28994c6d800 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/constant/Constants.java @@ -0,0 +1,216 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.constant; + +/** + * Server Constants + * @author Nacos + * + */ +public class Constants { + + public static final String CLIENT_VERSION_HEADER = "Client-Version"; + + public static final String CLIENT_VERSION = "3.0.0"; + + public static int DATA_IN_BODY_VERSION = 204; + + public static final String DEFAULT_GROUP = "DEFAULT_GROUP"; + + /** + * server端配置文件基目录 + */ + public static final String BASE_DIR = "config-data"; + + /** + * server端配置文件备份目录 + */ + public static final String CONFIG_BAK_DIR = System.getProperty("user.home", "/home/admin") + "/nacos/bak_data"; + + public static final String APPNAME = "AppName"; + + public static final String UNKNOWN_APP = "UnknownApp"; + + public static final String DEFAULT_DOMAINNAME = "commonconfig.config-host.taobao.com"; + + public static final String DAILY_DOMAINNAME = "commonconfig.taobao.net"; + + public static final int DEFAULT_PORT = 8080; + + public static final String NULL = ""; + + public static final String DATAID = "dataId"; + + public static final String GROUP = "group"; + + public static final String LAST_MODIFIED = "Last-Modified"; + + public static final String ACCEPT_ENCODING = "Accept-Encoding"; + + public static final String CONTENT_ENCODING = "Content-Encoding"; + + public static final String PROBE_MODIFY_REQUEST = "Listening-Configs"; + + public static final String PROBE_MODIFY_RESPONSE = "Probe-Modify-Response"; + + public static final String PROBE_MODIFY_RESPONSE_NEW = "Probe-Modify-Response-New"; + + public static final String USE_ZIP = "true"; + + public static final String CONTENT_MD5 = "Content-MD5"; + + public static final String CONFIG_VERSION = "Config-Version"; + + public static final String IF_MODIFIED_SINCE = "If-Modified-Since"; + + public static final String SPACING_INTERVAL = "client-spacing-interval"; + + /** + * 秒 + */ + public static final int ASYNC_UPDATE_ADDRESS_INTERVAL = 300; + /** + * 秒 + */ + public static final int POLLING_INTERVAL_TIME = 15; + /** + * 毫秒 + */ + public static final int ONCE_TIMEOUT = 2000; + /** + * 毫秒 + */ + public static final int CONN_TIMEOUT = 2000; + /** + * 毫秒 + */ + public static final int SO_TIMEOUT = 60000; + /** + * 毫秒 + */ + public static final int RECV_WAIT_TIMEOUT = ONCE_TIMEOUT * 5; + + public static final String BASE_PATH = "/v1/cs"; + + public static final String OPS_CONTROLLER_PATH = BASE_PATH + "/ops"; + + public static final String CAPACITY_CONTROLLER_PATH = BASE_PATH + "/capacity"; + + public static final String COMMUNICATION_CONTROLLER_PATH = BASE_PATH + "/communication"; + + public static final String CONFIG_CONTROLLER_PATH = BASE_PATH + "/configs"; + + public static final String HEALTH_CONTROLLER_PATH = BASE_PATH + "/health"; + + public static final String HISTORY_CONTROLLER_PATH = BASE_PATH + "/history"; + + public static final String LISTENER_CONTROLLER_PATH = BASE_PATH + "/listener"; + + public static final String ENCODE = "UTF-8"; + + public static final String MAP_FILE = "map-file.js"; + + public static final int FLOW_CONTROL_THRESHOLD = 20; + + public static final int FLOW_CONTROL_SLOT = 10; + + public static final int FLOW_CONTROL_INTERVAL = 1000; + + public static final String LINE_SEPARATOR = Character.toString((char) 1); + + public static final String WORD_SEPARATOR = Character.toString((char) 2); + + public static final String NACOS_LINE_SEPARATOR = "\r\n"; + + /** + * 从网络获取数据的总时间, 当超过此时间, 不再从网络获取数据, 单位ms + */ + public static final long TOTALTIME_FROM_SERVER = 10000; + /** + * 从网络获取数据的总时间的失效时间, 单位ms + */ + public static final long TOTALTIME_INVALID_THRESHOLD = 60000; + + /** + * 批量操作时, 单条数据的状态码 + */ + /** + * 发生异常 + */ + public static final int BATCH_OP_ERROR = -1; + public static final String BATCH_OP_ERROR_IO_MSG = "get config dump error"; + public static final String BATCH_OP_ERROR_CONFLICT_MSG = "config get conflicts"; + /** + * 查询成功, 数据存在 + */ + public static final int BATCH_QUERY_EXISTS = 1; + public static final String BATCH_QUERY_EXISTS_MSG = "config exits"; + /** + * 查询成功, 数据不存在 + */ + public static final int BATCH_QUERY_NONEXISTS = 2; + public static final String BATCH_QUERY_NONEEXISTS_MSG = "config not exits"; + /** + * 新增成功 + */ + public static final int BATCH_ADD_SUCCESS = 3; + /** + * 更新成功 + */ + public static final int BATCH_UPDATE_SUCCESS = 4; + + public static final int MAX_UPDATE_FAIL_COUNT = 5; + public static final int MAX_UPDATEALL_FAIL_COUNT = 5; + public static final int MAX_REMOVE_FAIL_COUNT = 5; + public static final int MAX_REMOVEALL_FAIL_COUNT = 5; + public static final int MAX_NOTIFY_COUNT = 5; + public static final int MAX_ADDACK_COUNT = 5; + + /** + * 数据的初始版本号 + */ + public static final int FIRST_VERSION = 1; + /** + * 数据被删除的标识版本号 + */ + public static final int POISON_VERSION = -1; + /** + * 写磁盘文件时, 临时版本号 + */ + public static final int TEMP_VERSION = 0; + /** + * 获取数据的顺序:容灾文件-> 服务器 -> 本地缓存 + */ + public static final int GETCONFIG_LOCAL_SERVER_SNAPSHOT = 1; + /** + * 获取数据的顺序:容灾文件-> 本地缓存 -> 服务器 + */ + public static final int GETCONFIG_LOCAL_SNAPSHOT_SERVER = 2; + + public static final String CLIENT_APPNAME_HEADER = "Client-AppName"; + public static final String CLIENT_REQUEST_TS_HEADER = "Client-RequestTS"; + public static final String CLIENT_REQUEST_TOKEN_HEADER = "Client-RequestToken"; + /** + * client, sdk请求server服务的身份 + */ + public static final String REQUEST_IDENTITY = "Request-Identity"; + /** + * 鉴权结果信息 + */ + public static final String ACL_RESPONSE = "ACL-Response"; + + public static final int ATOMIC_MAX_SIZE = 1000; +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/constant/CounterMode.java b/config/src/main/java/com/alibaba/nacos/config/server/constant/CounterMode.java new file mode 100644 index 00000000000..988e7d46b2f --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/constant/CounterMode.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.constant; + +/** + * counter mode + * + * @author hexu.hxy + * @date 2018/3/13 + */ +public enum CounterMode { + /** + * 增加 + */ + INCREMENT, + /** + * 减少 + */ + DECREMENT; + + public CounterMode reverse() { + if (INCREMENT == this) { + return DECREMENT; + } + return INCREMENT; + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/controller/CapacityController.java b/config/src/main/java/com/alibaba/nacos/config/server/controller/CapacityController.java new file mode 100644 index 00000000000..1c52b6c10df --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/controller/CapacityController.java @@ -0,0 +1,160 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.controller; + +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.model.RestResult; +import com.alibaba.nacos.config.server.model.capacity.Capacity; +import com.alibaba.nacos.config.server.service.capacity.CapacityService; + +/** + * capcity manage + * + * @author hexu.hxy + */ +@Controller +@RequestMapping(Constants.CAPACITY_CONTROLLER_PATH) +public class CapacityController { + + private static final Logger LOGGER = LoggerFactory.getLogger(CapacityController.class); + + @Autowired + private CapacityService capacityService; + + @ResponseBody + @RequestMapping(method = RequestMethod.GET) + public RestResult getCapacity(HttpServletResponse response, + @RequestParam(required = false) String group, + @RequestParam(required = false) String tenant) { + if (group == null && tenant == null) { + RestResult restResult = new RestResult(); + response.setStatus(400); + restResult.setCode(400); + restResult.setMessage("参数group和tenant不能同时为空"); + return restResult; + } + if (group == null && StringUtils.isBlank(tenant)) { + RestResult restResult = new RestResult(); + response.setStatus(400); + restResult.setCode(400); + restResult.setMessage("tenant不能为空字符串"); + return restResult; + } + RestResult restResult = new RestResult(); + try { + response.setStatus(200); + restResult.setCode(200); + Capacity capacity = capacityService.getCapacityWithDefault(group, tenant); + if (capacity == null) { + LOGGER.warn("[getCapacity] capacity不存在,需初始化 group: {}, tenant: {}", group, tenant); + capacityService.initCapacity(group, tenant); + capacity = capacityService.getCapacityWithDefault(group, tenant); + } + if (capacity != null) { + restResult.setData(capacity); + } + } catch (Exception e) { + LOGGER.error("[getCapacity] ", e); + response.setStatus(500); + restResult.setCode(500); + restResult.setMessage(e.getMessage()); + } + return restResult; + } + + /** + * 修改Group或租户的容量,容量信息还没有初始化的则初始化记录 + */ + @ResponseBody + @RequestMapping(method = RequestMethod.POST) + public RestResult updateCapacity(HttpServletResponse response, + @RequestParam(required = false) String group, + @RequestParam(required = false) String tenant, + @RequestParam(required = false) Integer quota, + @RequestParam(required = false) Integer maxSize, + @RequestParam(required = false) Integer maxAggrCount, + @RequestParam(required = false) Integer maxAggrSize) { + if (StringUtils.isBlank(group) && StringUtils.isBlank(tenant)) { + capacityService.initAllCapacity(); + RestResult restResult = new RestResult(); + setFailResult(response, restResult, 400); + restResult.setMessage("参数group和tenant不能同时为空"); + return restResult; + } + if (quota == null && maxSize == null && maxAggrCount == null && maxAggrSize == null) { + RestResult restResult = new RestResult(); + setFailResult(response, restResult, 400); + restResult.setMessage("参数quota、maxSize、maxAggrCount、maxAggrSize不能同时为空"); + return restResult; + } + String targetFieldName; + String targetFieldValue; + if (tenant == null) { + targetFieldName = "group"; + targetFieldValue = group; + } else { + targetFieldName = "tenant"; + targetFieldValue = tenant; + } + RestResult restResult = new RestResult(); + if (StringUtils.isBlank(targetFieldValue)) { + setFailResult(response, restResult, 400); + restResult.setMessage(String.format("参数%s为空", targetFieldName)); + return restResult; + } + try { + boolean insertOrUpdateResult = capacityService.insertOrUpdateCapacity(group, tenant, quota, maxSize, + maxAggrCount, maxAggrSize); + if (insertOrUpdateResult) { + setSuccessResult(response, restResult); + restResult.setMessage(String.format("成功更新%s为%s的容量信息配置", targetFieldName, targetFieldValue)); + return restResult; + } + setFailResult(response, restResult, 500); + restResult.setMessage(String.format("%s为%s的容量信息配置更新失败", targetFieldName, targetFieldValue)); + return restResult; + } catch (Exception e) { + LOGGER.error("[updateCapacity] ", e); + setFailResult(response, restResult, 500); + restResult.setMessage(e.getMessage()); + return restResult; + } + } + + private void setFailResult(HttpServletResponse response, RestResult restResult, int statusCode) { + response.setStatus(statusCode); + restResult.setCode(statusCode); + restResult.setData(false); + } + + private void setSuccessResult(HttpServletResponse response, RestResult restResult) { + response.setStatus(200); + restResult.setCode(200); + restResult.setData(true); + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/controller/CommunicationController.java b/config/src/main/java/com/alibaba/nacos/config/server/controller/CommunicationController.java new file mode 100755 index 00000000000..f1f6651b2a1 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/controller/CommunicationController.java @@ -0,0 +1,112 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.controller; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.model.SampleResult; +import com.alibaba.nacos.config.server.service.LongPullingService; +import com.alibaba.nacos.config.server.service.dump.DumpService; +import com.alibaba.nacos.config.server.service.notify.NotifyService; + + +/** + * 用于其他节点通知的控制器 + * + * @author boyan + * @date 2010-5-7 + */ +@Controller +@RequestMapping(Constants.COMMUNICATION_CONTROLLER_PATH) +public class CommunicationController { + + @Autowired + private DumpService dumpService; + + @Autowired + protected LongPullingService longPullingService; + + private String trueStr = "true"; + + /** + * 通知配置信息改变 + * + */ + @RequestMapping(value="/dataChange", method = RequestMethod.GET) + @ResponseBody + public Boolean notifyConfigInfo(HttpServletRequest request, HttpServletResponse response, + @RequestParam("dataId") String dataId, @RequestParam("group") String group, + @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant, + @RequestParam(value = "tag", required = false) String tag) { + dataId = dataId.trim(); + group = group.trim(); + String lastModified = request.getHeader(NotifyService.NOTIFY_HEADER_LAST_MODIFIED); + long lastModifiedTs = StringUtils.isEmpty(lastModified) ? -1 : Long.parseLong(lastModified); + String handleIp = request.getHeader(NotifyService.NOTIFY_HEADER_OP_HANDLE_IP); + String isBetaStr = request.getHeader("isBeta"); + if (StringUtils.isNotBlank(isBetaStr) && trueStr.equals(isBetaStr)) { + dumpService.dump(dataId, group, tenant, lastModifiedTs, handleIp, true); + } else { + dumpService.dump(dataId, group, tenant, tag, lastModifiedTs, handleIp); + } + return true; + } + + /** + * 在本台机器上获得订阅改配置的客户端信息 + */ + @RequestMapping(value="/configWatchers", method = RequestMethod.GET) + @ResponseBody + public SampleResult getSubClientConfig(HttpServletRequest request, + HttpServletResponse response, + @RequestParam("dataId") String dataId, + @RequestParam("group") String group, + @RequestParam(value = "tenant", required = false) String tenant, + ModelMap modelMap) + throws IOException, ServletException, Exception { + group = StringUtils.isBlank(group) ? Constants.DEFAULT_GROUP : group; + SampleResult sampleResult = longPullingService.getCollectSubscribleInfo(dataId, group, tenant); + return sampleResult; + } + + /** + * 在本台机器上获得客户端监听的配置列表 + */ + @RequestMapping(value= "/watcherConfigs", method = RequestMethod.GET) + @ResponseBody + public SampleResult getSubClientConfigByIp(HttpServletRequest request, + HttpServletResponse response, + @RequestParam("ip") String ip, + ModelMap modelMap) + throws IOException, ServletException, Exception { + SampleResult sampleResult = longPullingService.getCollectSubscribleInfoByIp(ip); + return sampleResult; + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigController.java b/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigController.java new file mode 100644 index 00000000000..6e364c2eb34 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigController.java @@ -0,0 +1,368 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.controller; + +import java.io.IOException; +import java.net.URLDecoder; +import java.sql.Timestamp; +import java.util.HashMap; +import java.util.Map; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.exception.NacosException; +import com.alibaba.nacos.config.server.model.ConfigAdvanceInfo; +import com.alibaba.nacos.config.server.model.ConfigInfo; +import com.alibaba.nacos.config.server.model.ConfigInfo4Beta; +import com.alibaba.nacos.config.server.model.GroupkeyListenserStatus; +import com.alibaba.nacos.config.server.model.Page; +import com.alibaba.nacos.config.server.model.RestResult; +import com.alibaba.nacos.config.server.model.SampleResult; +import com.alibaba.nacos.config.server.service.AggrWhitelist; +import com.alibaba.nacos.config.server.service.ConfigDataChangeEvent; +import com.alibaba.nacos.config.server.service.ConfigSubService; +import com.alibaba.nacos.config.server.service.PersistService; +import com.alibaba.nacos.config.server.service.merge.MergeDatumService; +import com.alibaba.nacos.config.server.service.trace.ConfigTraceService; +import com.alibaba.nacos.config.server.utils.MD5Util; +import com.alibaba.nacos.config.server.utils.ParamUtils; +import com.alibaba.nacos.config.server.utils.RequestUtil; +import com.alibaba.nacos.config.server.utils.SystemConfig; +import com.alibaba.nacos.config.server.utils.TimeUtils; +import com.alibaba.nacos.config.server.utils.event.EventDispatcher; + +/** + * 软负载客户端发布数据专用控制器 + * + * @author leiwen + * + */ +@Controller +@RequestMapping(Constants.CONFIG_CONTROLLER_PATH) +public class ConfigController extends HttpServlet { + /** + * uid + */ + private static final long serialVersionUID = 4339468526746635388L; + + private static final Logger log = LoggerFactory.getLogger(ConfigController.class); + + @Autowired + private transient ConfigServletInner inner; + + @Autowired + private transient PersistService persistService; + + @Autowired + private transient MergeDatumService mergeService; + + @Autowired + private transient ConfigSubService configSubService; + + /** + * 增加或更新非聚合数据。 + * + * @throws NacosException + */ + @RequestMapping(method = RequestMethod.POST) + @ResponseBody + public Boolean publishConfig(HttpServletRequest request, HttpServletResponse response, + @RequestParam("dataId") String dataId, @RequestParam("group") String group, + @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant, + @RequestParam("content") String content, + @RequestParam(value = "tag", required = false) String tag, + @RequestParam(value = "appName", required = false) String appName, + @RequestParam(value = "src_user", required = false) String srcUser, + @RequestParam(value = "config_tags", required = false) String configTags, + @RequestParam(value = "desc", required = false) String desc, + @RequestParam(value = "use", required = false) String use, + @RequestParam(value = "effect", required = false) String effect, + @RequestParam(value = "type", required = false) String type, + @RequestParam(value = "schema", required = false) String schema) throws NacosException { + final String srcIp = RequestUtil.getRemoteIp(request); + String requestIpApp = RequestUtil.getAppName(request); + ParamUtils.checkParam(dataId, group, "datumId", content); + ParamUtils.checkParam(tag); + + Map configAdvanceInfo = new HashMap(10); + if (configTags != null) { + configAdvanceInfo.put("config_tags", configTags); + } + if (desc != null) { + configAdvanceInfo.put("desc", desc); + } + if (use != null) { + configAdvanceInfo.put("use", use); + } + if (effect != null) { + configAdvanceInfo.put("effect", effect); + } + if (type != null) { + configAdvanceInfo.put("type", type); + } + if (schema != null) { + configAdvanceInfo.put("schema", schema); + } + ParamUtils.checkParam(configAdvanceInfo); + + if (AggrWhitelist.isAggrDataId(dataId)) { + log.warn("[aggr-conflict] {} attemp to publish single data, {}, {}", + new Object[] { RequestUtil.getRemoteIp(request), dataId, group }); + throw new NacosException(NacosException.NO_RIGHT, "dataId:" + dataId + " is aggr"); + } + + final Timestamp time = TimeUtils.getCurrentTime(); + String betaIps = request.getHeader("betaIps"); + ConfigInfo configInfo = new ConfigInfo(dataId, group, tenant, appName, content); + if (StringUtils.isBlank(betaIps)) { + if (StringUtils.isBlank(tag)) { + persistService.insertOrUpdate(srcIp, srcUser, configInfo, time, configAdvanceInfo, false); + EventDispatcher.fireEvent(new ConfigDataChangeEvent(false, dataId, group, tenant, time.getTime())); + } else { + persistService.insertOrUpdateTag(configInfo, tag, srcIp, srcUser, time, false); + EventDispatcher.fireEvent(new ConfigDataChangeEvent(false, dataId, group, tenant, tag, time.getTime())); + } + } else { // beta publish + persistService.insertOrUpdateBeta(configInfo, betaIps, srcIp, srcUser, time, false); + EventDispatcher.fireEvent(new ConfigDataChangeEvent(true, dataId, group, tenant, time.getTime())); + } + ConfigTraceService.logPersistenceEvent(dataId, group, tenant, requestIpApp, time.getTime(), + SystemConfig.LOCAL_IP, ConfigTraceService.PERSISTENCE_EVENT_PUB, content); + + return true; + } + + /** + * 取数据 + * + * @throws ServletException + * @throws IOException + * @throws NacosException + */ + @RequestMapping(method = RequestMethod.GET) + public void getConfig(HttpServletRequest request, HttpServletResponse response, + @RequestParam("dataId") String dataId, @RequestParam("group") String group, + @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant, + @RequestParam(value = "tag", required = false) String tag) + throws IOException, ServletException, NacosException { + // check params + ParamUtils.checkParam(dataId, group, "datumId", "content"); + ParamUtils.checkParam(tag); + + final String clientIp = RequestUtil.getRemoteIp(request); + inner.doGetConfig(request, response, dataId, group, tenant, tag, clientIp); + } + + /** + * 同步删除某个dataId下面所有的聚合前数据 + * + * @throws NacosException + */ + @RequestMapping(method = RequestMethod.DELETE) + @ResponseBody + public Boolean deleteConfig(HttpServletRequest request, HttpServletResponse response, + @RequestParam("dataId") String dataId, // + @RequestParam("group") String group, // + @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant, + @RequestParam(value = "tag", required = false) String tag) throws NacosException { + ParamUtils.checkParam(dataId, group, "datumId", "rm"); + ParamUtils.checkParam(tag); + String clientIp = RequestUtil.getRemoteIp(request); + if (StringUtils.isBlank(tag)) { + persistService.removeAggrConfigInfo(dataId, group, tenant); + } + mergeService.addMergeTask(dataId, group, tenant, tag, clientIp); + return true; + } + + + @RequestMapping(value = "/catalog", method = RequestMethod.GET) + @ResponseBody + public RestResult getConfigAdvanceInfo(HttpServletRequest request, HttpServletResponse response, + @RequestParam("dataId") String dataId, @RequestParam("group") String group, + @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant, + ModelMap modelMap) { + RestResult rr = new RestResult(); + ConfigAdvanceInfo configInfo = persistService.findConfigAdvanceInfo(dataId, group, tenant); + rr.setCode(200); + rr.setData(configInfo); + return rr; + } + + /** + * 比较MD5 + */ + @RequestMapping(value = "/listener", method = RequestMethod.POST) + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true); + String probeModify = request.getParameter("Listening-Configs"); + if (StringUtils.isBlank(probeModify)) { + throw new IllegalArgumentException("invalid probeModify"); + } + + probeModify = URLDecoder.decode(probeModify, Constants.ENCODE); + + Map clientMd5Map = null; + try { + clientMd5Map = MD5Util.getClientMd5Map(probeModify); + } catch (Throwable e) { + throw new IllegalArgumentException("invalid probeModify"); + } + + // do long-polling + inner.doPollingConfig(request, response, clientMd5Map, probeModify.length()); + } + + /* + * 订阅改配置的客户端信息 + */ + @RequestMapping(value = "/listener", method = RequestMethod.GET) + @ResponseBody + public GroupkeyListenserStatus getListeners(HttpServletRequest request, HttpServletResponse response, + @RequestParam("dataId") String dataId, @RequestParam("group") String group, + @RequestParam(value = "tenant", required = false) String tenant, + @RequestParam(value = "sampleTime", required = false, defaultValue = "1") int sampleTime, ModelMap modelMap) + throws IOException, ServletException, Exception { + group = StringUtils.isBlank(group) ? Constants.DEFAULT_GROUP : group; + SampleResult collectSampleResult = configSubService.getCollectSampleResult(dataId, group, tenant, sampleTime); + GroupkeyListenserStatus gls = new GroupkeyListenserStatus(); + gls.setCollectStatus(200); + if (collectSampleResult.getLisentersGroupkeyStatus() != null) { + gls.setLisentersGroupkeyStatus(collectSampleResult.getLisentersGroupkeyStatus()); + } + return gls; + } + + /** + * 查询配置信息,返回JSON格式。 + */ + @RequestMapping(params = "search=accurate", method = RequestMethod.GET) + @ResponseBody + public Page searchConfig(HttpServletRequest request, + HttpServletResponse response, + @RequestParam("dataId") String dataId, + @RequestParam("group") String group, + @RequestParam(value = "appName", required = false) String appName, + @RequestParam(value = "tenant", required = false, defaultValue=StringUtils.EMPTY) String tenant, + @RequestParam(value = "config_tags", required = false) String configTags, + @RequestParam("pageNo") int pageNo, + @RequestParam("pageSize") int pageSize, ModelMap modelMap) { + Map configAdvanceInfo = new HashMap(100); + if (StringUtils.isNotBlank(appName)) { + configAdvanceInfo.put("appName", appName); + } + if (StringUtils.isNotBlank(configTags)) { + configAdvanceInfo.put("config_tags", configTags); + } + try { + Page page = persistService.findConfigInfo4Page(pageNo, pageSize, dataId, group, tenant, + configAdvanceInfo); + return page; + } catch (Exception e) { + String errorMsg = "serialize page error, dataId=" + dataId + ", group=" + group; + log.error(errorMsg, e); + throw new RuntimeException(errorMsg, e); + } + } + + /** + * 模糊查询配置信息。不允许只根据内容模糊查询,即dataId和group都为NULL,但content不是NULL。这种情况下,返回所有配置。 + */ + @RequestMapping(params = "search=blur", method = RequestMethod.GET) + @ResponseBody + public Page fuzzySearchConfig(HttpServletRequest request, HttpServletResponse response, + @RequestParam("dataId") String dataId, // + @RequestParam("group") String group, // + @RequestParam(value = "appName", required = false) String appName, + @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant, + @RequestParam(value = "config_tags", required = false) String configTags, + @RequestParam("pageNo") int pageNo, // + @RequestParam("pageSize") int pageSize, // + ModelMap modelMap) { + Map configAdvanceInfo = new HashMap(50); + if (StringUtils.isNotBlank(appName)) { + configAdvanceInfo.put("appName", appName); + } + if (StringUtils.isNotBlank(configTags)) { + configAdvanceInfo.put("config_tags", configTags); + } + try { + Page page = persistService.findConfigInfoLike4Page(pageNo, pageSize, dataId, group, tenant, + configAdvanceInfo); + return page; + } catch (Exception e) { + String errorMsg = "serialize page error, dataId=" + dataId + ", group=" + group; + log.error(errorMsg, e); + throw new RuntimeException(errorMsg, e); + } + } + + @RequestMapping(params = "beta=true", method = RequestMethod.DELETE) + @ResponseBody + public RestResult stopBeta(HttpServletRequest request, HttpServletResponse response, + @RequestParam(value = "dataId") String dataId, @RequestParam(value = "group") String group, + @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant) { + RestResult rr = new RestResult(); + try { + persistService.removeConfigInfo4Beta(dataId, group, tenant); + } catch (Throwable e) { + log.error("remove beta data error", e); + rr.setCode(500); + rr.setData(false); + rr.setMessage("remove beta data error"); + return rr; + } + EventDispatcher.fireEvent(new ConfigDataChangeEvent(true, dataId, group, tenant, System.currentTimeMillis())); + rr.setCode(200); + rr.setData(true); + rr.setMessage("stop beta ok"); + return rr; + } + + @RequestMapping(params = "beta=true", method = RequestMethod.GET) + @ResponseBody + public RestResult queryBeta(HttpServletRequest request, HttpServletResponse response, + @RequestParam(value = "dataId") String dataId, @RequestParam(value = "group") String group, + @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant) { + RestResult rr = new RestResult(); + try { + ConfigInfo4Beta ci = persistService.findConfigInfo4Beta(dataId, group, tenant); + rr.setCode(200); + rr.setData(ci); + rr.setMessage("stop beta ok"); + return rr; + } catch (Throwable e) { + log.error("remove beta data error", e); + rr.setCode(500); + rr.setMessage("remove beta data error"); + return rr; + } + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigServletInner.java b/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigServletInner.java new file mode 100755 index 00000000000..9049312a931 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigServletInner.java @@ -0,0 +1,343 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.controller; + +import static com.alibaba.nacos.config.server.utils.LogUtil.pullLog; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.URLEncoder; +import java.nio.channels.Channels; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.model.CacheItem; +import com.alibaba.nacos.config.server.model.ConfigInfoBase; +import com.alibaba.nacos.config.server.service.ConfigService; +import com.alibaba.nacos.config.server.service.DiskUtil; +import com.alibaba.nacos.config.server.service.LongPullingService; +import com.alibaba.nacos.config.server.service.PersistService; +import com.alibaba.nacos.config.server.service.trace.ConfigTraceService; +import com.alibaba.nacos.config.server.utils.GroupKey2; +import com.alibaba.nacos.config.server.utils.LogUtil; +import com.alibaba.nacos.config.server.utils.MD5Util; +import com.alibaba.nacos.config.server.utils.PropertyUtil; +import com.alibaba.nacos.config.server.utils.Protocol; +import com.alibaba.nacos.config.server.utils.RequestUtil; +import com.alibaba.nacos.config.server.utils.TimeUtils; + +/** + * ConfigServlet inner for aop + * + * @author Nacos + */ +@Service +public class ConfigServletInner { + + @Autowired + private LongPullingService longPullingService; + + @Autowired + private PersistService persistService; + + private static final int TRY_GET_LOCK_TIMES = 9; + + private static final int START_LONGPULLING_VERSION_NUM = 204; + /** + * 轮询接口 + */ + public String doPollingConfig(HttpServletRequest request, HttpServletResponse response, Map clientMd5Map, int probeRequestSize) throws IOException, ServletException { + + // 长轮询 + if (LongPullingService.isSupportLongPulling(request)) { + longPullingService.addLongPullingClient(request, response, clientMd5Map, probeRequestSize); + return HttpServletResponse.SC_OK + ""; + } + + // else 兼容短轮询逻辑 + List changedGroups = MD5Util.compareMd5(request, response, clientMd5Map); + + // 兼容短轮询result + String oldResult = MD5Util.compareMd5OldResult(changedGroups); + String newResult = MD5Util.compareMd5ResultString(changedGroups); + + String version = request.getHeader(Constants.CLIENT_VERSION_HEADER); + if (version == null) { + version = "2.0.0"; + } + int versionNum = Protocol.getVersionNumber(version); + + /** + * 2.0.4版本以前, 返回值放入header中 + */ + if (versionNum < START_LONGPULLING_VERSION_NUM) { + response.addHeader(Constants.PROBE_MODIFY_RESPONSE, oldResult); + response.addHeader(Constants.PROBE_MODIFY_RESPONSE_NEW, newResult); + } else { + request.setAttribute("content", newResult); + } + + // 禁用缓存 + response.setHeader("Pragma", "no-cache"); + response.setDateHeader("Expires", 0); + response.setHeader("Cache-Control", "no-cache,no-store"); + response.setStatus(HttpServletResponse.SC_OK); + return HttpServletResponse.SC_OK + ""; + } + + /** + * 同步配置获取接口 + */ + public String doGetConfig(HttpServletRequest request, HttpServletResponse response, String dataId, String group, + String tenant, String tag, String clientIp) throws IOException, ServletException { + final String groupKey = GroupKey2.getKey(dataId, group, tenant); + String autoTag = request.getHeader("Vipserver-Tag"); + String requestIpApp = RequestUtil.getAppName(request); + int lockResult = tryConfigReadLock(request, response, groupKey); + + final String requestIp = RequestUtil.getRemoteIp(request); + boolean isBeta = false; + if (lockResult > 0) { + FileInputStream fis = null; + try { + String md5 = Constants.NULL; + long lastModified = 0L; + CacheItem cacheItem = ConfigService.getContentCache(groupKey); + if (cacheItem != null) { + if (cacheItem.isBeta()) { + if (cacheItem.getIps4Beta().contains(clientIp)) { + isBeta = true; + } + } + } + File file = null; + ConfigInfoBase configInfoBase = null; + PrintWriter out = null; + if (isBeta) { + md5 = cacheItem.getMd54Beta(); + lastModified = cacheItem.getLastModifiedTs4Beta(); + if (PropertyUtil.isStandaloneMode()) { + configInfoBase = persistService.findConfigInfo4Beta(dataId, group,tenant); + } else { + file = DiskUtil.targetBetaFile(dataId, group, tenant); + } + response.setHeader("isBeta", "true"); + } else { + if (StringUtils.isBlank(tag)) { + if (isUseTag(cacheItem, autoTag)) { + if (cacheItem != null) { + if (cacheItem.tagMd5 != null) { + md5 = cacheItem.tagMd5.get(autoTag); + } + if (cacheItem.tagLastModifiedTs != null) { + lastModified = cacheItem.tagLastModifiedTs.get(autoTag); + } + } + if (PropertyUtil.isStandaloneMode()) { + configInfoBase = persistService.findConfigInfo4Tag(dataId, group, tenant, autoTag); + } else { + file = DiskUtil.targetTagFile(dataId, group, tenant, autoTag); + } + + response.setHeader("Vipserver-Tag", + URLEncoder.encode(autoTag, StandardCharsets.UTF_8.displayName())); + } else { + md5 = cacheItem.getMd5(); + lastModified = cacheItem.getLastModifiedTs(); + if (PropertyUtil.isStandaloneMode()) { + configInfoBase = persistService.findConfigInfo(dataId, group, tenant); + } else { + file = DiskUtil.targetFile(dataId, group, tenant); + } + if (configInfoBase == null && fileNotExist(file)) { + // FIXME CacheItem + // 不存在了无法简单的计算推送delayed,这里简单的记做-1 + ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, -1, + ConfigTraceService.PULL_EVENT_NOTFOUND, -1, requestIp); + + // pullLog.info("[client-get] clientIp={}, {}, + // no data", + // new Object[]{clientIp, groupKey}); + + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + response.getWriter().println("config data not exist"); + return HttpServletResponse.SC_NOT_FOUND + ""; + } + } + } else { + if (cacheItem != null) { + if (cacheItem.tagMd5 != null) { + md5 = cacheItem.tagMd5.get(tag); + } + if (cacheItem.tagLastModifiedTs != null) { + Long lm = cacheItem.tagLastModifiedTs.get(tag); + if (lm != null) { + lastModified = lm; + } + } + } + if (PropertyUtil.isStandaloneMode()) { + configInfoBase = persistService.findConfigInfo4Tag(dataId, group, tenant, tag); + } else { + file = DiskUtil.targetTagFile(dataId, group, tenant, tag); + } + if (configInfoBase == null && fileNotExist(file)) { + // FIXME CacheItem + // 不存在了无法简单的计算推送delayed,这里简单的记做-1 + ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, -1, ConfigTraceService.PULL_EVENT_NOTFOUND, + -1, requestIp); + + // pullLog.info("[client-get] clientIp={}, {}, + // no data", + // new Object[]{clientIp, groupKey}); + + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + response.getWriter().println("config data not exist"); + return HttpServletResponse.SC_NOT_FOUND + ""; + } + } + } + + response.setHeader(Constants.CONTENT_MD5, md5); + /** + * 禁用缓存 + */ + response.setHeader("Pragma", "no-cache"); + response.setDateHeader("Expires", 0); + response.setHeader("Cache-Control", "no-cache,no-store"); + if (PropertyUtil.isStandaloneMode()) { + response.setDateHeader("Last-Modified", lastModified); + } else { + fis = new FileInputStream(file); + response.setDateHeader("Last-Modified", file.lastModified()); + } + + + if (PropertyUtil.isStandaloneMode()) { + out = response.getWriter(); + out.print(configInfoBase.getContent()); + out.flush(); + out.close(); + } else { + fis.getChannel().transferTo(0L, fis.getChannel().size(), + Channels.newChannel(response.getOutputStream())); + } + + LogUtil.pullCheckLog.warn("{}|{}|{}|{}", new Object[]{groupKey,requestIp,md5, TimeUtils.getCurrentTimeStr()}); + + + final long delayed = System.currentTimeMillis() - lastModified; + + // TODO distinguish pull-get && push-get + // 否则无法直接把delayed作为推送延时的依据,因为主动get请求的delayed值都很大 + ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, lastModified, ConfigTraceService.PULL_EVENT_OK, delayed, + requestIp); + + } finally { + releaseConfigReadLock(groupKey); + if (null != fis) { + fis.close(); + } + } + } else if (lockResult == 0) { + + // FIXME CacheItem 不存在了无法简单的计算推送delayed,这里简单的记做-1 + ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, -1, ConfigTraceService.PULL_EVENT_NOTFOUND, -1, requestIp); + + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + response.getWriter().println("config data not exist"); + return HttpServletResponse.SC_NOT_FOUND + ""; + + } else { + + pullLog.info("[client-get] clientIp={}, {}, get data during dump", new Object[]{clientIp, groupKey}); + + response.setStatus(HttpServletResponse.SC_CONFLICT); + response.getWriter().println("requested file is being modified, please try later."); + return HttpServletResponse.SC_CONFLICT + ""; + + } + + return HttpServletResponse.SC_OK + ""; + } + + private static void releaseConfigReadLock(String groupKey) { + ConfigService.releaseReadLock(groupKey); + } + + private static int tryConfigReadLock(HttpServletRequest request, HttpServletResponse response, String groupKey) throws IOException, ServletException { + /** + * 默认加锁失败 + */ + int lockResult = -1; + /** + * 尝试加锁,最多10次 + */ + for (int i = TRY_GET_LOCK_TIMES; i >= 0; --i) { + lockResult = ConfigService.tryReadLock(groupKey); + /** + * 数据不存在 + */ + if (0 == lockResult) { + break; + } + + /** + * success + */ + if (lockResult > 0) { + break; + } + /** + * retry + */ + if (i > 0) { + try { + Thread.sleep(1); + } catch (Exception e) { + } + } + } + + return lockResult; + } + + private static boolean isUseTag(CacheItem cacheItem, String tag) { + if (cacheItem != null && cacheItem.tagMd5 != null && cacheItem.tagMd5.size() > 0) { + if (StringUtils.isNotBlank(tag) && cacheItem.tagMd5.containsKey(tag)) { + return true; + } + } + return false; + } + + private static boolean fileNotExist(File file) { + return file == null || !file.exists(); + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/controller/HealthController.java b/config/src/main/java/com/alibaba/nacos/config/server/controller/HealthController.java new file mode 100644 index 00000000000..68a2f1b01f1 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/controller/HealthController.java @@ -0,0 +1,79 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.controller; + +import javax.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.service.DataSourceService; +import com.alibaba.nacos.config.server.service.DynamicDataSource; +import com.alibaba.nacos.config.server.service.ServerListService; +import com.alibaba.nacos.config.server.utils.SystemConfig; + +/** + * health service + * + * @author Nacos + * + */ +@Controller +@RequestMapping(Constants.HEALTH_CONTROLLER_PATH) +public class HealthController { + + @Autowired + private DynamicDataSource dynamicDataSource; + private DataSourceService dataSourceService; + private String heathUpStr = "UP"; + private String heathDownStr = "DOWN"; + private String heathWarnStr = "WARN"; + + @PostConstruct + public void init() { + dataSourceService = dynamicDataSource.getDataSource(); + } + + @ResponseBody + @RequestMapping(method = RequestMethod.GET) + public String getHealth() { + // TODO UP DOWN WARN + StringBuilder sb = new StringBuilder(); + String dbStatus = dataSourceService.getHealth(); + if (dbStatus.contains(heathUpStr) && ServerListService.isAddressServerHealth() && ServerListService.isInIpList()) { + sb.append(heathUpStr); + } else if (dbStatus.contains(heathWarnStr) && ServerListService.isAddressServerHealth() && ServerListService.isInIpList()){ + sb.append("WARN:"); + sb.append("从数据库 ").append(dbStatus.split(":")[1]).append(" down. "); + } else { + sb.append("DOWN:"); + if (dbStatus.indexOf(heathDownStr) != -1) { + sb.append("主数据库 ").append(dbStatus.split(":")[1]).append(" down. "); + } + if (!ServerListService.isAddressServerHealth()) { + sb.append("地址服务器 down. "); + } + if (!ServerListService.isInIpList()) { + sb.append("server ").append(SystemConfig.LOCAL_IP).append(" 不在地址服务器的IP列表中. "); + } + } + + return sb.toString(); + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/controller/HistoryController.java b/config/src/main/java/com/alibaba/nacos/config/server/controller/HistoryController.java new file mode 100644 index 00000000000..ae38b1b76c5 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/controller/HistoryController.java @@ -0,0 +1,61 @@ +package com.alibaba.nacos.config.server.controller; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.model.ConfigHistoryInfo; +import com.alibaba.nacos.config.server.model.Page; +import com.alibaba.nacos.config.server.service.PersistService; + +/** + * 管理控制器。 + * + *@author Nacos + */ +@Controller +@RequestMapping(Constants.HISTORY_CONTROLLER_PATH) +public class HistoryController { + + @Autowired + protected PersistService persistService; + + @RequestMapping(params = "search=accurate", method = RequestMethod.GET) + @ResponseBody + public Page listConfigHistory(HttpServletRequest request, HttpServletResponse response, + @RequestParam("dataId") String dataId, // + @RequestParam("group") String group, // + @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant, + @RequestParam(value = "appName", required = false) String appName, + @RequestParam(value = "pageNo", required = false) Integer pageNo, // + @RequestParam(value = "pageSize", required = false) Integer pageSize, // + ModelMap modelMap) { + pageNo = null == pageNo ? Integer.valueOf(1) : pageNo; + pageSize = null == pageSize ? Integer.valueOf(100) : pageSize; + pageSize = pageSize > 500 ? Integer.valueOf(500) : pageSize; + // configInfoBase没有appName字段 + Page page = persistService.findConfigHistory(dataId, group, tenant, pageNo, pageSize); + return page; + } + + /** + * 查看配置历史信息详情 + */ + @RequestMapping(method = RequestMethod.GET) + @ResponseBody + public ConfigHistoryInfo getConfigHistoryInfo(HttpServletRequest request, HttpServletResponse response, + @RequestParam("nid") Long nid, ModelMap modelMap) { + ConfigHistoryInfo configInfo = persistService.detailConfigHistory(nid); + return configInfo; + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/controller/ListenerController.java b/config/src/main/java/com/alibaba/nacos/config/server/controller/ListenerController.java new file mode 100755 index 00000000000..9a8a08644c1 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/controller/ListenerController.java @@ -0,0 +1,96 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.controller; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.model.GroupkeyListenserStatus; +import com.alibaba.nacos.config.server.model.SampleResult; +import com.alibaba.nacos.config.server.service.ConfigSubService; +import com.alibaba.nacos.config.server.utils.GroupKey2; + +/** + * Config longpulling + * + * @author Nacos + * + */ +@Controller +@RequestMapping(Constants.LISTENER_CONTROLLER_PATH) +public class ListenerController { + + @Autowired + ConfigSubService configSubService; + + /* + * 获取客户端订阅配置信息 + */ + @RequestMapping(method = RequestMethod.GET) + @ResponseBody + public GroupkeyListenserStatus getAllSubClientConfigByIp(HttpServletRequest request, HttpServletResponse response, + @RequestParam("ip") String ip, @RequestParam(value = "all", required = false) boolean all, + @RequestParam(value = "tenant", required = false) String tenant, + @RequestParam(value = "sampleTime", required = false, defaultValue = "1") int sampleTime, ModelMap modelMap) + throws IOException, ServletException, Exception { + SampleResult collectSampleResult = configSubService.getCollectSampleResultByIp(ip, sampleTime); + GroupkeyListenserStatus gls = new GroupkeyListenserStatus(); + gls.setCollectStatus(200); + Map configMd5Status = new HashMap(100); + if (collectSampleResult.getLisentersGroupkeyStatus() != null) { + Map status = collectSampleResult.getLisentersGroupkeyStatus(); + for (Map.Entry config : status.entrySet()) { + if (!StringUtils.isBlank(tenant)) { + if (config.getKey().contains(tenant)) { + configMd5Status.put(config.getKey(), config.getValue()); + } + } else { + // 默认值获取公共配置,如果想看所有配置,要加all + if (all) { + configMd5Status.put(config.getKey(), config.getValue()); + } else { + String[] configKeys = GroupKey2.parseKey(config.getKey()); + if (StringUtils.isBlank(configKeys[2])) { + configMd5Status.put(config.getKey(), config.getValue()); + } + } + } + } + gls.setLisentersGroupkeyStatus(configMd5Status); + } + + return gls; + } + + + +} + diff --git a/config/src/main/java/com/alibaba/nacos/config/server/controller/OpsController.java b/config/src/main/java/com/alibaba/nacos/config/server/controller/OpsController.java new file mode 100755 index 00000000000..56d4a0d5739 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/controller/OpsController.java @@ -0,0 +1,60 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.controller; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; + +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.service.PersistService; +import com.alibaba.nacos.config.server.service.dump.DumpService; + +/** + * 管理控制器。 + * + * @author Nacos + */ +@Controller +@RequestMapping(Constants.OPS_CONTROLLER_PATH) +public class OpsController { + + private static final Logger log = LoggerFactory.getLogger(OpsController.class); + + @Autowired + protected PersistService persistService; + + @Autowired + DumpService dumpService; + + // ops call + @RequestMapping(value = "/localCache", method = RequestMethod.POST) + @ResponseBody + public String updateLocalCacheFromStore(HttpServletRequest request, HttpServletResponse respons) { + log.info("start to dump all data from store."); + dumpService.dumpAll(); + log.info("finish to dump all data from store."); + return HttpServletResponse.SC_OK + ""; + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/exception/GlobalExceptionHandler.java b/config/src/main/java/com/alibaba/nacos/config/server/exception/GlobalExceptionHandler.java new file mode 100644 index 00000000000..52dce3509a2 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/exception/GlobalExceptionHandler.java @@ -0,0 +1,67 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.exception; + +import java.io.IOException; + +import javax.servlet.http.HttpServletResponse; + +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +/** + * global exception handler + * + * @author Nacos + * + */ +@ControllerAdvice +public class GlobalExceptionHandler { + + /** + * For IllegalArgumentException, we are returning void with status code as + * 400, so our error-page will be used in this case. + * + * @throws IllegalArgumentException + * + */ + @ExceptionHandler(IllegalArgumentException.class) + public void handleIllegalArgumentException(HttpServletResponse response, Exception ex) throws IOException { + response.setStatus(400); + if (ex.getMessage() != null) { + response.getWriter().println(ex.getMessage()); + } else { + response.getWriter().println("invalid param"); + } + } + + /** + * For NacosException + * + * @throws NacosException + * + */ + @ExceptionHandler(NacosException.class) + public void handleNacosException(HttpServletResponse response, NacosException ex) throws IOException { + response.setStatus(ex.getErrCode()); + if (ex.getErrMsg() != null) { + response.getWriter().println(ex.getErrMsg()); + } else { + response.getWriter().println("unknown exception"); + } + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/exception/NacosException.java b/config/src/main/java/com/alibaba/nacos/config/server/exception/NacosException.java new file mode 100644 index 00000000000..0e0c7c668ad --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/exception/NacosException.java @@ -0,0 +1,94 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.exception; + +/** + * Nacos exception + * + * @author Nacos + * + */ +public class NacosException extends Exception { + + /** + * serialVersionUID + */ + private static final long serialVersionUID = -3913902031489277776L; + + private int errCode; + + private String errMsg; + + public NacosException() { + } + + public NacosException(int errCode, String errMsg) { + super(errMsg); + this.errCode = errCode; + this.errMsg = errMsg; + } + + public int getErrCode() { + return errCode; + } + + public String getErrMsg() { + return errMsg; + } + + public void setErrCode(int errCode) { + this.errCode = errCode; + } + + public void setErrMsg(String errMsg) { + this.errMsg = errMsg; + } + + @Override + public String toString() { + return "ErrCode:" + errCode + ",ErrMsg:" + errMsg; + } + + /** + * server error code, use http code 400 403 throw exception to user 500 502 + * 503 change ip and retry + */ + /** + * invalid param(参数错误) + */ + public static final int INVALID_PARAM = 400; + /** + * no right(鉴权失败) + */ + public static final int NO_RIGHT = 403; + /** + * conflict(写并发冲突) + */ + public static final int CONFLICT = 409; + /** + * server error(server异常,如超时) + */ + public static final int SERVER_ERROR = 500; + /** + * bad gateway(路由异常,如nginx后面的Server挂掉) + */ + public static final int BAD_GATEWAY = 502; + /** + * over threshold(超过server端的限流阈值) + */ + public static final int OVER_THRESHOLD = 503; + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/filter/WebFilter.java b/config/src/main/java/com/alibaba/nacos/config/server/filter/WebFilter.java new file mode 100644 index 00000000000..f27d9f3355e --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/filter/WebFilter.java @@ -0,0 +1,84 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.filter; + +import static com.alibaba.nacos.config.server.utils.LogUtil.defaultLog; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import org.springframework.core.annotation.Order; +import com.alibaba.nacos.config.server.constant.Constants; + +/** + * encode filter + * + * @author Nacos + * + */ +@Order(1) +@javax.servlet.annotation.WebFilter(filterName = "webFilter", urlPatterns = "/*") +public class WebFilter implements Filter { + + static private String webRootPath; + + static public String rootPath() { + return webRootPath; + } + + /** + * 方便测试 + * @param path web path + */ + static public void setWebRootPath(String path) { + webRootPath = path; + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + ServletContext ctx = filterConfig.getServletContext(); + setWebRootPath(ctx.getRealPath("/")); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + request.setCharacterEncoding(Constants.ENCODE); + response.setContentType("application/json;charset="+Constants.ENCODE); + + try { + chain.doFilter(request, response); + } catch (IOException ioe) { + defaultLog.debug("Filter catch exception, " + ioe.toString(), ioe); + throw ioe; + } catch (ServletException se) { + defaultLog.debug("Filter catch exception, " + se.toString(), se); + throw se; + } + } + + + @Override + public void destroy() { + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/manager/AbstractTask.java b/config/src/main/java/com/alibaba/nacos/config/server/manager/AbstractTask.java new file mode 100644 index 00000000000..e5e79b7a95f --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/manager/AbstractTask.java @@ -0,0 +1,64 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.manager; + +/** + * task manage + * + * @author huali + * + */ +public abstract class AbstractTask { + /** + * 一个任务两次处理的间隔,单位是毫秒 + */ + private long taskInterval; + + /** + * 任务上次被处理的时间,用毫秒表示 + */ + private long lastProcessTime; + /** + * merge task + * @param task task + */ + public abstract void merge(AbstractTask task); + + public void setTaskInterval(long interval){ + this.taskInterval = interval; + } + + public long getTaskInterval(){ + return this.taskInterval; + } + + public void setLastProcessTime(long lastProcessTime){ + this.lastProcessTime = lastProcessTime; + } + + public long getLastProcessTime(){ + return this.lastProcessTime; + } + + /** + * TaskManager 判断当前是否需要处理这个Task,子类可以Override这个函数实现自己的逻辑 + * @return + */ + public boolean shouldProcess(){ + return (System.currentTimeMillis() - this.lastProcessTime >= this.taskInterval); + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/manager/TaskManager.java b/config/src/main/java/com/alibaba/nacos/config/server/manager/TaskManager.java new file mode 100644 index 00000000000..5042e4c39e5 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/manager/TaskManager.java @@ -0,0 +1,298 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.manager; + +import java.lang.management.ManagementFactory; +import java.util.Date; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; +import javax.management.ObjectName; +import org.slf4j.Logger; + +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.utils.LogUtil; + + +/** + * 用于处理一定要执行成功的任务 单线程的方式处理任务,保证任务一定被成功处理 + * + * @author huali + * + */ +public final class TaskManager implements TaskManagerMBean { + + private static final Logger log =LogUtil.defaultLog; + + private final ConcurrentHashMap tasks = new ConcurrentHashMap(); + + private final ConcurrentHashMap taskProcessors = + new ConcurrentHashMap(); + + private TaskProcessor defaultTaskProcessor; + + Thread processingThread; + + private final AtomicBoolean closed = new AtomicBoolean(true); + + private String name; + + class ProcessRunnable implements Runnable { + + public void run() { + while (!TaskManager.this.closed.get()) { + try { + Thread.sleep(100); + TaskManager.this.process(); + } + catch (Throwable e) { + } + } + + } + + } + + ReentrantLock lock = new ReentrantLock(); + + Condition notEmpty = this.lock.newCondition(); + + + public TaskManager() { + this(null); + } + + + public AbstractTask getTask(String type) { + return this.tasks.get(type); + } + + + public TaskProcessor getTaskProcessor(String type) { + return this.taskProcessors.get(type); + } + + @SuppressWarnings("PMD.AvoidManuallyCreateThreadRule") + public TaskManager(String name) { + this.name = name; + if (null != name && name.length() > 0) { + this.processingThread = new Thread(new ProcessRunnable(), name); + } + else { + this.processingThread = new Thread(new ProcessRunnable()); + } + this.processingThread.setDaemon(true); + this.closed.set(false); + this.processingThread.start(); + } + + public int size() { + return tasks.size(); + } + + public void close() { + this.closed.set(true); + this.processingThread.interrupt(); + } + + + public void await() throws InterruptedException { + this.lock.lock(); + try { + while (!this.isEmpty()) { + this.notEmpty.await(); + } + } + finally { + this.lock.unlock(); + } + } + + + public boolean await(long timeout, TimeUnit unit) throws InterruptedException { + this.lock.lock(); + boolean isawait = false; + try { + while (!this.isEmpty()) { + isawait = this.notEmpty.await(timeout, unit); + } + return isawait; + } finally { + this.lock.unlock(); + } + } + + + public void addProcessor(String type, TaskProcessor taskProcessor) { + this.taskProcessors.put(type, taskProcessor); + } + + + public void removeProcessor(String type) { + this.taskProcessors.remove(type); + } + + + public void removeTask(String type) { + this.lock.lock(); + try { + this.tasks.remove(type); + } + finally { + this.lock.unlock(); + } + } + + + /** + * 将任务加入到任务Map中 + * + * @param type + * @param task + * @param previousTask + * */ + public void addTask(String type, AbstractTask task) { + this.lock.lock(); + try { + AbstractTask oldTask = tasks.put(type, task); + if (null != oldTask) { + task.merge(oldTask); + } + } finally { + this.lock.unlock(); + } + } + + + /** + * + */ + protected void process() { + for (Map.Entry entry : this.tasks.entrySet()) { + AbstractTask task = null; + this.lock.lock(); + try { + // 获取任务 + task = entry.getValue(); + if (null != task) { + if (!task.shouldProcess()) { + // 任务当前不需要被执行,直接跳过 + continue; + } + // 先将任务从任务Map中删除 + this.tasks.remove(entry.getKey()); + } + } + finally { + this.lock.unlock(); + } + + if (null != task) { + // 获取任务处理器 + TaskProcessor processor = this.taskProcessors.get(entry.getKey()); + if (null == processor) { + // 如果没有根据任务类型设置的处理器,使用默认处理器 + processor = this.getDefaultTaskProcessor(); + } + if (null != processor) { + boolean result = false; + try { + // 处理任务 + result = processor.process(entry.getKey(), task); + } + catch (Throwable t) { + log.error("task_fail", "处理task失败", t); + } + if (!result) { + // 任务处理失败,设置最后处理时间 + task.setLastProcessTime(System.currentTimeMillis()); + + // 将任务重新加入到任务Map中 + this.addTask(entry.getKey(), task); + } + } + } + } + + if (tasks.isEmpty()) { + this.lock.lock(); + try { + this.notEmpty.signalAll(); + } + finally { + this.lock.unlock(); + } + } + } + + + public boolean isEmpty() { + return tasks.isEmpty(); + } + + + public TaskProcessor getDefaultTaskProcessor() { + this.lock.lock(); + try { + return this.defaultTaskProcessor; + } + finally { + this.lock.unlock(); + } + } + + + public void setDefaultTaskProcessor(TaskProcessor defaultTaskProcessor) { + this.lock.lock(); + try { + this.defaultTaskProcessor = defaultTaskProcessor; + } + finally { + this.lock.unlock(); + } + } + + + public String getTaskInfos() { + StringBuilder sb = new StringBuilder(); + for(String taskType: this.taskProcessors.keySet()) { + sb.append(taskType).append(":"); + AbstractTask task = this.tasks.get(taskType); + if(task != null) { + sb.append(new Date(task.getLastProcessTime()).toString()); + } else { + sb.append("finished"); + } + sb.append(Constants.NACOS_LINE_SEPARATOR); + } + + return sb.toString(); + } + + + public void init() { + try { + ObjectName oName = new ObjectName(this.name + ":type=" + TaskManager.class.getSimpleName()); + ManagementFactory.getPlatformMBeanServer().registerMBean(this, oName); + } + catch (Exception e) { + log.error("registerMBean_fail", "注册mbean出错", e); + } + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/manager/TaskManagerMBean.java b/config/src/main/java/com/alibaba/nacos/config/server/manager/TaskManagerMBean.java new file mode 100644 index 00000000000..363ddbd6e66 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/manager/TaskManagerMBean.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.manager; + +/** + * tasks + * + * @author Nacos + * + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public interface TaskManagerMBean { + + /** + * get task info + * + * @return info + */ + public String getTaskInfos(); + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/manager/TaskProcessor.java b/config/src/main/java/com/alibaba/nacos/config/server/manager/TaskProcessor.java new file mode 100644 index 00000000000..bb79d5efe28 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/manager/TaskProcessor.java @@ -0,0 +1,35 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.manager; + +/** + * task processor + * + * @author Nacos + * + */ +public interface TaskProcessor { + /** + * process task + * + * @param taskType + * task type + * @param task + * task + * @return process task result + */ + boolean process(String taskType, AbstractTask task); +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/ACLInfo.java b/config/src/main/java/com/alibaba/nacos/config/server/model/ACLInfo.java new file mode 100644 index 00000000000..cb4c99e1142 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/ACLInfo.java @@ -0,0 +1,47 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model; + +import java.util.List; + +/** + * acl info + * + * @author Nacos + * + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public class ACLInfo { + + private Boolean isOpen; + private List ips; + + public List getIps() { + return ips; + } + + public void setIps(List ips) { + this.ips = ips; + } + + public Boolean getIsOpen() { + return isOpen; + } + + public void setIsOpen(Boolean isOpen) { + this.isOpen = isOpen; + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/AuthType.java b/config/src/main/java/com/alibaba/nacos/config/server/model/AuthType.java new file mode 100644 index 00000000000..73b2be45df5 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/AuthType.java @@ -0,0 +1,29 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model; + +/** + * auth type + * + * @author Nacos + * + */ +public enum AuthType { + /** + * auth type + */ + GROUP, GROUP_DATAID, TENANT_GROUP, TENANT +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/CacheItem.java b/config/src/main/java/com/alibaba/nacos/config/server/model/CacheItem.java new file mode 100644 index 00000000000..cc1c294a444 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/CacheItem.java @@ -0,0 +1,109 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model; + +import java.util.List; +import java.util.Map; + +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.utils.SimpleReadWriteLock; +import com.alibaba.nacos.config.server.utils.SingletonRepository.DataIdGroupIdCache; +/** + * cache item + * @author Nacos + * + */ +public class CacheItem { + + public CacheItem(String groupKey) { + this.groupKey = DataIdGroupIdCache.getSingleton(groupKey); + } + + public String getMd5() { + return md5; + } + public void setMd5(String md5) { + this.md5 = md5; + } + public long getLastModifiedTs() { + return lastModifiedTs; + } + public void setLastModifiedTs(long lastModifiedTs) { + this.lastModifiedTs = lastModifiedTs; + } + public boolean isBeta() { + return isBeta; + } + public void setBeta(boolean isBeta) { + this.isBeta = isBeta; + } + public String getMd54Beta() { + return md54Beta; + } + public void setMd54Beta(String md54Beta) { + this.md54Beta = md54Beta; + } + public List getIps4Beta() { + return ips4Beta; + } + public void setIps4Beta(List ips4Beta) { + this.ips4Beta = ips4Beta; + } + public long getLastModifiedTs4Beta() { + return lastModifiedTs4Beta; + } + public void setLastModifiedTs4Beta(long lastModifiedTs4Beta) { + this.lastModifiedTs4Beta = lastModifiedTs4Beta; + } + public SimpleReadWriteLock getRwLock() { + return rwLock; + } + public void setRwLock(SimpleReadWriteLock rwLock) { + this.rwLock = rwLock; + } + public String getGroupKey() { + return groupKey; + } + public Map getTagMd5() { + return tagMd5; + } + public Map getTagLastModifiedTs() { + return tagLastModifiedTs; + } + public void setTagMd5(Map tagMd5) { + this.tagMd5 = tagMd5; + } + public void setTagLastModifiedTs(Map tagLastModifiedTs) { + this.tagLastModifiedTs = tagLastModifiedTs; + } + + final String groupKey; + public volatile String md5 = Constants.NULL; + public volatile long lastModifiedTs; + + /** + * use for beta + */ + public volatile boolean isBeta = false; + public volatile String md54Beta = Constants.NULL; + public volatile List ips4Beta; + public volatile long lastModifiedTs4Beta; + public volatile Map tagMd5; + public volatile Map tagLastModifiedTs; + public SimpleReadWriteLock rwLock = new SimpleReadWriteLock(); + + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigAdvanceInfo.java b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigAdvanceInfo.java new file mode 100644 index 00000000000..991e04c83e4 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigAdvanceInfo.java @@ -0,0 +1,100 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model; + +import java.io.Serializable; + +/** + * Config advance info + * + * @author Nacos + * + */ +public class ConfigAdvanceInfo implements Serializable { + static final long serialVersionUID = -1L; + private long createTime; + private long modifyTime; + private String createUser; + private String createIp; + private String desc; + private String use; + private String effect; + private String type; + private String schema; + private String configTags; + public long getCreateTime() { + return createTime; + } + public void setCreateTime(long createTime) { + this.createTime = createTime; + } + public long getModifyTime() { + return modifyTime; + } + public void setModifyTime(long modifyTime) { + this.modifyTime = modifyTime; + } + public String getCreateUser() { + return createUser; + } + public void setCreateUser(String createUser) { + this.createUser = createUser; + } + public String getCreateIp() { + return createIp; + } + public void setCreateIp(String createIp) { + this.createIp = createIp; + } + public String getDesc() { + return desc; + } + public void setDesc(String desc) { + this.desc = desc; + } + public String getUse() { + return use; + } + public void setUse(String use) { + this.use = use; + } + public String getEffect() { + return effect; + } + public void setEffect(String effect) { + this.effect = effect; + } + public String getType() { + return type; + } + public void setType(String type) { + this.type = type; + } + public String getSchema() { + return schema; + } + public void setSchema(String schema) { + this.schema = schema; + } + public String getConfigTags() { + return configTags; + } + public void setConfigTags(String configTags) { + this.configTags = configTags; + } + + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigHistoryInfo.java b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigHistoryInfo.java new file mode 100644 index 00000000000..d2bcbb24601 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigHistoryInfo.java @@ -0,0 +1,163 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model; + +import java.sql.Timestamp; + +/** + * history Info + * @author Nacos + * + */ +public class ConfigHistoryInfo { + + /** + * id, nid, + * data_id, group_id, + * content, md5, + * gmt_create, gmt_modified, (配置创建时间,配置变更时间) + * src_user, src_ip, (变更操作者) + * op_type(变更操作类型) + */ + + private long id; + /** + * 上次改动历史的id + */ + private long lastId = -1; + + private String dataId; + private String group; + private String tenant; + private String appName; + private String md5; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public long getLastId() { + return lastId; + } + + public void setLastId(long lastId) { + this.lastId = lastId; + } + + public String getDataId() { + return dataId; + } + + public void setDataId(String dataId) { + this.dataId = dataId; + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + public String getTenant() { + return tenant; + } + + public void setTenant(String tenant) { + this.tenant = tenant; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getSrcIp() { + return srcIp; + } + + public void setSrcIp(String srcIp) { + this.srcIp = srcIp; + } + + public String getSrcUser() { + return srcUser; + } + + public void setSrcUser(String srcUser) { + this.srcUser = srcUser; + } + + public String getOpType() { + return opType; + } + + public void setOpType(String opType) { + this.opType = opType; + } + + public Timestamp getCreatedTime() { + return new Timestamp(createdTime.getTime()); + } + + public void setCreatedTime(Timestamp createdTime) { + this.createdTime = new Timestamp(createdTime.getTime()); + } + + public Timestamp getLastModifiedTime() { + return new Timestamp(lastModifiedTime.getTime()); + } + + public void setLastModifiedTime(Timestamp lastModifiedTime) { + this.lastModifiedTime = new Timestamp(lastModifiedTime.getTime()); + } + + public String getAppName() { + return appName; + } + + public void setAppName(String appName) { + this.appName = appName; + } + + public String getMd5() { + return md5; + } + + public void setMd5(String md5) { + this.md5 = md5; + } + + private String content; + + private String srcIp; + private String srcUser; + /** + * 操作类型, 包括插入、更新、删除 + */ + private String opType; + + private Timestamp createdTime; + private Timestamp lastModifiedTime; +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfo.java b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfo.java new file mode 100644 index 00000000000..81675373342 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfo.java @@ -0,0 +1,83 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model; + +/** + * 配置信息类 + * + * @author boyan + * @date 2010-5-4 + */ +public class ConfigInfo extends ConfigInfoBase { + static final long serialVersionUID = -1L; + + private String tenant; + + private String appName; + + public ConfigInfo() { + + } + + public ConfigInfo(String dataId, String group, String content) { + super(dataId, group, content); + } + + public ConfigInfo(String dataId, String group, String appName, String content) { + super(dataId, group, content); + this.appName = appName; + } + + public ConfigInfo(String dataId, String group, String tenant, String appName, String content) { + super(dataId, group, content); + this.tenant = tenant; + this.appName = appName; + } + + public String getTenant() { + return tenant; + } + + public void setTenant(String tenant) { + this.tenant = tenant; + } + + public String getAppName() { + return appName; + } + + public void setAppName(String appName) { + this.appName = appName; + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } + + @Override + public String toString() { + return "ConfigInfo{" + "id=" + getId() + ", dataId='" + getDataId() + '\'' + ", group='" + getGroup() + '\'' + + ", tenant='" + tenant + '\'' + ", appName='" + appName + '\'' + ", content='" + getContent() + '\'' + + ", md5='" + getMd5() + '\'' + '}'; + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfo4Beta.java b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfo4Beta.java new file mode 100644 index 00000000000..26743da0484 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfo4Beta.java @@ -0,0 +1,58 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model; + +/** + * beta Info + * @author Nacos + * + */ +public class ConfigInfo4Beta extends ConfigInfo { + + /** + * + */ + private static final long serialVersionUID = 296578467953931353L; + + private String betaIps; + + + public ConfigInfo4Beta() { + } + + public ConfigInfo4Beta(String dataId, String group, String appName, String content, String betaIps) { + super(dataId, group, appName, content); + this.betaIps = betaIps; + } + + public String getBetaIps() { + return betaIps; + } + + public void setBetaIps(String betaIps) { + this.betaIps = betaIps; + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfo4Tag.java b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfo4Tag.java new file mode 100644 index 00000000000..f84bf7e5a91 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfo4Tag.java @@ -0,0 +1,58 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model; +/** + * tag info + * @author Nacos + * + */ +public class ConfigInfo4Tag extends ConfigInfo { + + /** + * + */ + private static final long serialVersionUID = 296578467953931353L; + + private String tag; + + + public ConfigInfo4Tag() { + } + + public ConfigInfo4Tag(String dataId, String group, String tag, String appName, String content) { + super(dataId, group, appName, content); + this.tag = tag; + } + + public String getTag() { + return tag; + } + + public void setTag(String tag) { + this.tag = tag; + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoAggr.java b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoAggr.java new file mode 100644 index 00000000000..fd5d6d406ad --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoAggr.java @@ -0,0 +1,196 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model; + +import java.io.Serializable; + + +/** + * 聚合前的配置信息类 + * + * @author leiwen.zh + * + */ +public class ConfigInfoAggr implements Serializable { + + private static final long serialVersionUID = -3845825581059306364L; + + private long id; + + private String dataId; + private String group; + private String datumId; + private String tenant; + private String appName; + private String content; + + + public ConfigInfoAggr(String dataId, String group, String datumId, String content) { + this.dataId = dataId; + this.group = group; + this.datumId = datumId; + this.content = content; + } + + public ConfigInfoAggr(String dataId, String group, String datumId, String appName, String content) { + this.dataId = dataId; + this.group = group; + this.datumId = datumId; + this.appName = appName; + this.content = content; + } + + + public ConfigInfoAggr() { + + } + + + public long getId() { + return id; + } + + + public void setId(long id) { + this.id = id; + } + + + public String getDataId() { + return dataId; + } + + + public void setDataId(String dataId) { + this.dataId = dataId; + } + + + public String getGroup() { + return group; + } + + + public void setGroup(String group) { + this.group = group; + } + + + public String getDatumId() { + return datumId; + } + + + public void setDatumId(String datumId) { + this.datumId = datumId; + } + + + public String getContent() { + return content; + } + + + public void setContent(String content) { + this.content = content; + } + + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((content == null) ? 0 : content.hashCode()); + result = prime * result + ((dataId == null) ? 0 : dataId.hashCode()); + result = prime * result + ((datumId == null) ? 0 : datumId.hashCode()); + result = prime * result + ((group == null) ? 0 : group.hashCode()); + return result; + } + + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ConfigInfoAggr other = (ConfigInfoAggr) obj; + if (content == null) { + if (other.content != null) { + return false; + } + } + else if (!content.equals(other.content)) { + return false; + } + if (dataId == null) { + if (other.dataId != null) { + return false; + } + } + else if (!dataId.equals(other.dataId)) { + return false; + } + if (datumId == null) { + if (other.datumId != null) { + return false; + } + } + else if (!datumId.equals(other.datumId)) { + return false; + } + if (group == null) { + if (other.group != null) { + return false; + } + } + else if (!group.equals(other.group)) { + return false; + } + return true; + } + + + @Override + public String toString() { + return "ConfigInfoAggr [dataId=" + dataId + ", group=" + group + ", datumId=" + datumId + ", content=" + + content + "]"; + } + + + public String getAppName() { + return appName; + } + + + public void setAppName(String appName) { + this.appName = appName; + } + + public String getTenant() { + return tenant; + } + + public void setTenant(String tenant) { + this.tenant = tenant; + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoBase.java b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoBase.java new file mode 100644 index 00000000000..fdf519f5f82 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoBase.java @@ -0,0 +1,215 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model; + +import java.io.PrintWriter; +import java.io.Serializable; + +import com.alibaba.nacos.config.server.utils.MD5; + +/** + * 不能增加字段,为了兼容老前台接口(老接口增加一个字段会出现不兼容问题)设置的model。 + * + * @author Nacos + * + */ +public class ConfigInfoBase implements Serializable, Comparable { + static final long serialVersionUID = -1L; + + /** + * 不能增加字段 + */ + private long id; + private String dataId; + private String group; + private String content; + private String md5; + + public ConfigInfoBase() { + + } + + public ConfigInfoBase(String dataId, String group, String content) { + this.dataId = dataId; + this.group = group; + this.content = content; + if (this.content != null) { + this.md5 = MD5.getInstance().getMD5String(this.content); + } + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getDataId() { + return dataId; + } + + public void setDataId(String dataId) { + this.dataId = dataId; + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getMd5() { + return md5; + } + + public void setMd5(String md5) { + this.md5 = md5; + } + + public void dump(PrintWriter writer) { + writer.write(this.content); + } + + public int compareTo(ConfigInfoBase o) { + if (o == null) { + return 1; + } + if (this.dataId == null ){ + if (o.getDataId() == null) { + return 0; + } else { + return -1; + } + } else { + if (o.getDataId() == null) { + return 1; + } else { + int cmpDataId = this.dataId.compareTo(o.getDataId()); + if (cmpDataId != 0) { + return cmpDataId; + } + } + } + + if (this.group == null) { + if (o.getGroup() == null) { + return 0; + } else { + return -1; + } + } else { + if (o.getGroup() == null) { + return 1; + } else { + int cmpGroup = this.group.compareTo(o.getGroup()); + if (cmpGroup != 0) { + return cmpGroup; + } + } + } + + if (this.content == null) { + if (o.getContent() == null) { + return 0; + } else { + return -1; + } + } else { + if (o.getContent() == null) { + return 1; + } else { + int cmpContent = this.content.compareTo(o.getContent()); + if (cmpContent != 0) { + return cmpContent; + } + } + } + return 0; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((content == null) ? 0 : content.hashCode()); + result = prime * result + ((dataId == null) ? 0 : dataId.hashCode()); + result = prime * result + ((group == null) ? 0 : group.hashCode()); + result = prime * result + ((md5 == null) ? 0 : md5.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ConfigInfoBase other = (ConfigInfoBase) obj; + if (content == null) { + if (other.content != null) { + return false; + } + } else if (!content.equals(other.content)) { + return false; + } + if (dataId == null) { + if (other.dataId != null) { + return false; + } + } else if (!dataId.equals(other.dataId)) { + return false; + } + if (group == null) { + if (other.group != null) { + return false; + } + } else if (!group.equals(other.group)) { + return false; + } + if (md5 == null) { + if (other.md5 != null) { + return false; + } + } else if (!md5.equals(other.md5)) { + return false; + } + return true; + } + + @Override + public String toString() { + return "ConfigInfoBase{" + "id=" + id + ", dataId='" + dataId + '\'' + + ", group='" + group + '\'' + ", content='" + content + '\'' + + ", md5='" + md5 + '\'' + '}'; + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoBaseEx.java b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoBaseEx.java new file mode 100644 index 00000000000..9c0e037dfbc --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoBaseEx.java @@ -0,0 +1,84 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model; + +/** + * 不能增加字段,为了兼容老前台接口(老接口增加一个字段会出现不兼容问题)设置的model。 + * + * @author Nacos + * + */ +public class ConfigInfoBaseEx extends ConfigInfoBase { + + private static final long serialVersionUID = -1L; + //不能增加字段 + /** + * 批量查询时, 单条数据的状态码, 具体的状态码在Constants.java中 + */ + private int status; + /** + * 批量查询时, 单条数据的信息 + */ + private String message; + + public ConfigInfoBaseEx() { + super(); + } + + public ConfigInfoBaseEx(String dataId, String group, String content) { + super(dataId, group, content); + } + + public ConfigInfoBaseEx(String dataId, String group, String content, + int status, String message) { + super(dataId, group, content); + this.status = status; + this.message = message; + } + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public String toString() { + return "ConfigInfoBaseEx [status=" + status + ", message=" + message + + ", dataId=" + getDataId() + ", group()=" + getGroup() + + ", content()=" + getContent() + "]"; + } + + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoChanged.java b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoChanged.java new file mode 100644 index 00000000000..539e890a9b7 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoChanged.java @@ -0,0 +1,119 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model; + +/** + * 变化的配置信息, 聚合时使用 + * + * @author leiwen.zh + * + */ +public class ConfigInfoChanged { + + private String dataId; + private String group; + private String tenant; + + public ConfigInfoChanged(String dataId, String group, String tenant) { + this.dataId = dataId; + this.group = group; + this.setTenant(tenant); + } + + + public ConfigInfoChanged() { + + } + + + public String getDataId() { + return dataId; + } + + + public void setDataId(String dataId) { + this.dataId = dataId; + } + + + public String getGroup() { + return group; + } + + + public void setGroup(String group) { + this.group = group; + } + + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((dataId == null) ? 0 : dataId.hashCode()); + result = prime * result + ((group == null) ? 0 : group.hashCode()); + return result; + } + + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ConfigInfoChanged other = (ConfigInfoChanged) obj; + if (dataId == null) { + if (other.dataId != null) { + return false; + } + } + else if (!dataId.equals(other.dataId)) { + return false; + } + if (group == null) { + if (other.group != null) { + return false; + } + } + else if (!group.equals(other.group)) { + return false; + } + return true; + } + + + @Override + public String toString() { + return "ConfigInfoChanged [dataId=" + dataId + ", group=" + group + "]"; + } + + + public String getTenant() { + return tenant; + } + + + public void setTenant(String tenant) { + this.tenant = tenant; + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoEx.java b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoEx.java new file mode 100644 index 00000000000..15ce6d0680d --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoEx.java @@ -0,0 +1,89 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model; + +/** + * ConfigInfo的扩展类, 用于批量处理 + * + * @author leiwen.zh + * + */ +public class ConfigInfoEx extends ConfigInfo { + + private static final long serialVersionUID = -1L; + + /** + * 批量查询时, 单条数据的状态码, 具体的状态码在Constants.java中 + */ + private int status; + /** + * 批量查询时, 单条数据的信息 + */ + private String message; + + public ConfigInfoEx() { + super(); + } + + public ConfigInfoEx(String dataId, String group, String content) { + super(dataId, group, content); + } + + public ConfigInfoEx(String dataId, String group, String content, int status, String message){ + super(dataId, group, content); + this.status = status; + this.message = message; + } + + + public int getStatus() { + return status; + } + + + public void setStatus(int status) { + this.status = status; + } + + + public String getMessage() { + return message; + } + + + public void setMessage(String message) { + this.message = message; + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } + + @Override + public String toString() { + return "ConfigInfoEx [status=" + status + ", message=" + message + + ", dataId=" + getDataId() + ", group=" + getGroup() + + ", appName=" + getAppName() + ", content=" + getContent() + + "]"; + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoWrapper.java b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoWrapper.java new file mode 100644 index 00000000000..d0faac5e61c --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoWrapper.java @@ -0,0 +1,47 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model; +/** + * ConfigInfo Wrapper + * @author Nacos + * + */ +public class ConfigInfoWrapper extends ConfigInfo { + private static final long serialVersionUID = 4511997359365712505L; + + private long lastModified; + + public ConfigInfoWrapper() { + } + + public long getLastModified() { + return lastModified; + } + + public void setLastModified(long lastModified) { + this.lastModified = lastModified; + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigKey.java b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigKey.java new file mode 100644 index 00000000000..f726b816557 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigKey.java @@ -0,0 +1,70 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model; + +import java.io.Serializable; + +/** + * config key + * + * @author Nacos + * + */ +public class ConfigKey implements Serializable { + + /** + * + */ + private static final long serialVersionUID = -1748953484511867580L; + + private String appName; + private String dataId; + private String group; + + public ConfigKey() { + }; + + public ConfigKey(String appName, String dataId, String group) { + this.appName = appName; + this.dataId = dataId; + this.group = group; + } + + public String getAppName() { + return appName; + } + + public void setAppName(String appName) { + this.appName = appName; + } + + public String getDataId() { + return dataId; + } + + public void setDataId(String dataId) { + this.dataId = dataId; + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/GroupInfo.java b/config/src/main/java/com/alibaba/nacos/config/server/model/GroupInfo.java new file mode 100644 index 00000000000..916b954a9a6 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/GroupInfo.java @@ -0,0 +1,135 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model; + +import java.io.Serializable; +/** + * group info + * @author Nacos + * + */ +public class GroupInfo implements Serializable { + static final long serialVersionUID = -1L; + private long id; + private String address; + private String group; + private String dataId; + + + public GroupInfo() { + + } + + + public GroupInfo(String address, String dataId, String group) { + super(); + this.address = address; + this.group = group; + this.dataId = dataId; + } + + + public long getId() { + return id; + } + + + public void setId(long id) { + this.id = id; + } + + + public String getAddress() { + return address; + } + + + public void setAddress(String address) { + this.address = address; + } + + + public String getGroup() { + return group; + } + + + public void setGroup(String group) { + this.group = group; + } + + + public String getDataId() { + return dataId; + } + + + public void setDataId(String dataId) { + this.dataId = dataId; + } + + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((address == null) ? 0 : address.hashCode()); + result = prime * result + ((dataId == null) ? 0 : dataId.hashCode()); + result = prime * result + ((group == null) ? 0 : group.hashCode()); + return result; + } + + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + GroupInfo other = (GroupInfo) obj; + if (address == null) { + if (other.address != null) { + return false; + } + } + else if (!address.equals(other.address)) { + return false; + } + if (dataId == null) { + if (other.dataId != null) { + return false; + } + } + else if (!dataId.equals(other.dataId)) { + return false; + } + if (group == null) { + if (other.group != null) { + return false; + } + } + else if (!group.equals(other.group)) { + return false; + } + return true; + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/GroupkeyListenserStatus.java b/config/src/main/java/com/alibaba/nacos/config/server/model/GroupkeyListenserStatus.java new file mode 100644 index 00000000000..2ea42722d6e --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/GroupkeyListenserStatus.java @@ -0,0 +1,54 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model; + +import java.io.Serializable; +import java.util.Map; + +/** + * litener status + * + * @author Nacos + * + */ +public class GroupkeyListenserStatus implements Serializable{ + + /** + * 随机数 + */ + private static final long serialVersionUID = -2094829323598842474L; + + private int collectStatus; + + private Map lisentersGroupkeyStatus; + + public int getCollectStatus() { + return collectStatus; + } + + public void setCollectStatus(int collectStatus) { + this.collectStatus = collectStatus; + } + + public Map getLisentersGroupkeyStatus() { + return lisentersGroupkeyStatus; + } + + public void setLisentersGroupkeyStatus( + Map lisentersGroupkeyStatus) { + this.lisentersGroupkeyStatus = lisentersGroupkeyStatus; + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/HistoryContext.java b/config/src/main/java/com/alibaba/nacos/config/server/model/HistoryContext.java new file mode 100644 index 00000000000..d3b9d5ca254 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/HistoryContext.java @@ -0,0 +1,121 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model; + +/** + * history context + * + * @author Nacos + * + */ +public class HistoryContext { + public String serverId; + public String dataId; + public String group; + public String tenant; + private String appName; + public boolean success; + public int statusCode; + public String statusMsg; + public Page configs; + + + public HistoryContext(String serverId, String dataId, String group, int statusCode, String statusMsg, + Page configs) { + this.serverId = serverId; + this.dataId = dataId; + this.group = group; + this.statusCode = statusCode; + this.statusMsg = statusMsg; + this.configs = configs; + this.success = 200 == statusCode; + } + + public HistoryContext() { + } + + public String getServerId() { + return serverId; + } + + public void setServerId(String serverId) { + this.serverId = serverId; + } + + public String getDataId() { + return dataId; + } + + public void setDataId(String dataId) { + this.dataId = dataId; + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + public String getTenant() { + return tenant; + } + + public void setTenant(String tenant) { + this.tenant = tenant; + } + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public int getStatusCode() { + return statusCode; + } + + public void setStatusCode(int statusCode) { + this.statusCode = statusCode; + } + + public String getStatusMsg() { + return statusMsg; + } + + public void setStatusMsg(String statusMsg) { + this.statusMsg = statusMsg; + } + + public Page getConfigs() { + return configs; + } + + public void setConfigs(Page configs) { + this.configs = configs; + } + + public String getAppName() { + return appName; + } + + public void setAppName(String appName) { + this.appName = appName; + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/Page.java b/config/src/main/java/com/alibaba/nacos/config/server/model/Page.java new file mode 100644 index 00000000000..770a33f573c --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/Page.java @@ -0,0 +1,88 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + + +/** + * 分页对象 + * + * @author boyan + * @date 2010-5-6 + * @param + */ +public class Page implements Serializable { + static final long serialVersionUID = -1L; + /** + * 总记录数 + */ + private int totalCount; + /** + * 页数 + */ + private int pageNumber; + /** + * 总页数 + */ + private int pagesAvailable; + /** + * 该页内容 + */ + private List pageItems = new ArrayList(); + + + public void setPageNumber(int pageNumber) { + this.pageNumber = pageNumber; + } + + + public void setPagesAvailable(int pagesAvailable) { + this.pagesAvailable = pagesAvailable; + } + + + public void setPageItems(List pageItems) { + this.pageItems = pageItems; + } + + + public int getTotalCount() { + return totalCount; + } + + + public void setTotalCount(int totalCount) { + this.totalCount = totalCount; + } + + + public int getPageNumber() { + return pageNumber; + } + + + public int getPagesAvailable() { + return pagesAvailable; + } + + + public List getPageItems() { + return pageItems; + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/RestPageResult.java b/config/src/main/java/com/alibaba/nacos/config/server/model/RestPageResult.java new file mode 100644 index 00000000000..e8056d78dc7 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/RestPageResult.java @@ -0,0 +1,83 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model; + +import java.io.Serializable; + +/** + * rest page result + * + * @author Nacos + * + * @param + * data type + */ +public class RestPageResult implements Serializable { + + /** + * + */ + private static final long serialVersionUID = -8048577763828650575L; + + private int code; + private String message; + private int total; + private int pageSize; + private int currentPage; + private T data; + public int getCode() { + return code; + } + public void setCode(int code) { + this.code = code; + } + public String getMessage() { + return message; + } + public void setMessage(String message) { + this.message = message; + } + + public int getTotal() { + return total; + } + public void setTotal(int total) { + this.total = total; + } + public int getPageSize() { + return pageSize; + } + public void setPageSize(int pageSize) { + this.pageSize = pageSize; + } + public int getCurrentPage() { + return currentPage; + } + public void setCurrentPage(int currentPage) { + this.currentPage = currentPage; + } + public T getData() { + return data; + } + public void setData(T data) { + this.data = data; + } + public static long getSerialversionuid() { + return serialVersionUID; + } + + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/RestResult.java b/config/src/main/java/com/alibaba/nacos/config/server/model/RestResult.java new file mode 100644 index 00000000000..50016a3f7b4 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/RestResult.java @@ -0,0 +1,83 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model; + +import java.io.Serializable; + +/** + * rest result class + * + * @author Nacos + * + * @param data type + */ + +public class RestResult implements Serializable { + + /** + * + */ + private static final long serialVersionUID = 6095433538316185017L; + + private int code; + private String message; + private T data; + + public RestResult() { + } + + public RestResult(int code, String message, T data) { + this.code = code; + this.setMessage(message); + this.data = data; + } + + public RestResult(int code, T data) { + this.code = code; + this.data = data; + } + + public RestResult(int code, String message) { + this.code = code; + this.setMessage(message); + } + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public T getData() { + return data; + } + + public void setData(T data) { + this.data = data; + } + + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/SampleResult.java b/config/src/main/java/com/alibaba/nacos/config/server/model/SampleResult.java new file mode 100644 index 00000000000..499b0a81ac7 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/SampleResult.java @@ -0,0 +1,43 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model; + +import java.io.Serializable; +import java.util.Map; +/** + * sample result + * @author Nacos + * + */ +public class SampleResult implements Serializable{ + + /** + * 随机数 + */ + private static final long serialVersionUID = 2587823382317389453L; + + private Map lisentersGroupkeyStatus; + + public Map getLisentersGroupkeyStatus() { + return lisentersGroupkeyStatus; + } + + public void setLisentersGroupkeyStatus( + Map lisentersGroupkeyStatus) { + this.lisentersGroupkeyStatus = lisentersGroupkeyStatus; + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/SubInfo.java b/config/src/main/java/com/alibaba/nacos/config/server/model/SubInfo.java new file mode 100644 index 00000000000..0d06efb6a89 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/SubInfo.java @@ -0,0 +1,63 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model; + +import java.sql.Timestamp; + +/** + * sub 数据结构体 + * @author Nacos + * + */ +public class SubInfo { + + private String appName; + private String dataId; + private String group; + private String localIp; + private Timestamp date; + public String getAppName() { + return appName; + } + public String getDataId() { + return dataId; + } + public String getGroup() { + return group; + } + public Timestamp getDate() { + return new Timestamp(date.getTime()); + } + public void setAppName(String appName) { + this.appName = appName; + } + public void setDataId(String dataId) { + this.dataId = dataId; + } + public void setGroup(String group) { + this.group = group; + } + public void setDate(Timestamp date) { + this.date = new Timestamp(date.getTime()); + } + public String getLocalIp() { + return localIp; + } + public void setLocalIp(String localIp) { + this.localIp = localIp; + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/SubscriberStatus.java b/config/src/main/java/com/alibaba/nacos/config/server/model/SubscriberStatus.java new file mode 100644 index 00000000000..9d49d11835e --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/SubscriberStatus.java @@ -0,0 +1,79 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model; + +/** + * subcriber status + * @author Nacos + * + */ +public class SubscriberStatus { + String groupKey; + String md5; + Long lastTime; + Boolean status; + String serverIp; + + public SubscriberStatus(){} + + public SubscriberStatus(String groupKey, Boolean status, String md5, Long lastTime) { + this.groupKey = groupKey; + this.md5 = md5; + this.lastTime = lastTime; + this.status = status; + } + + public String getMd5() { + return md5; + } + + public void setMd5(String md5) { + this.md5 = md5; + } + + public Long getLastTime() { + return lastTime; + } + + public void setLastTime(Long lastTime) { + this.lastTime = lastTime; + } + + public Boolean getStatus() { + return status; + } + + public void setStatus(Boolean status) { + this.status = status; + } + + public String getGroupKey() { + + return groupKey; + } + + public void setGroupKey(String groupKey) { + this.groupKey = groupKey; + } + + public String getServerIp() { + return serverIp; + } + + public void setServerIp(String serverIp) { + this.serverIp = serverIp; + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/app/ApplicationInfo.java b/config/src/main/java/com/alibaba/nacos/config/server/model/app/ApplicationInfo.java new file mode 100644 index 00000000000..ef096814978 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/app/ApplicationInfo.java @@ -0,0 +1,112 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model.app; + +import com.alibaba.nacos.config.server.utils.SystemConfig; + +/** + * app info + * + * @author Nacos + * + */ +public class ApplicationInfo { + + private static final long LOCK_EXPIRE_DURATION = 30 * 1000; + private static final long RECENTLY_DURATION = 24 * 60 * 60 * 1000; + + private String appName; + + private boolean isDynamicCollectDisabled = false; + + private long lastSubscribeInfoCollectedTime = 0L; + + private String subInfoCollectLockOwner = null; + + private long subInfoCollectLockExpireTime = 0L; + + public ApplicationInfo(String appName) { + this.appName = appName; + } + + public String getAppName() { + return appName; + } + + public void setAppName(String appName) { + this.appName = appName; + } + + public boolean isDynamicCollectDisabled() { + return isDynamicCollectDisabled; + } + + public void setDynamicCollectDisabled(boolean isDynamicCollectDisabled) { + this.isDynamicCollectDisabled = isDynamicCollectDisabled; + } + + public long getLastSubscribeInfoCollectedTime() { + return lastSubscribeInfoCollectedTime; + } + + public void setLastSubscribeInfoCollectedTime( + long lastSubscribeInfoCollectedTime) { + this.lastSubscribeInfoCollectedTime = lastSubscribeInfoCollectedTime; + } + + public String getSubInfoCollectLockOwner() { + return subInfoCollectLockOwner; + } + + public void setSubInfoCollectLockOwner(String subInfoCollectLockOwner) { + this.subInfoCollectLockOwner = subInfoCollectLockOwner; + } + + public long getSubInfoCollectLockExpireTime() { + return subInfoCollectLockExpireTime; + } + + public void setSubInfoCollectLockExpireTime( + long subInfoCollectLockExpireTime) { + this.subInfoCollectLockExpireTime = subInfoCollectLockExpireTime; + } + + public boolean isSubInfoRecentlyCollected() { + if (System.currentTimeMillis() - this.lastSubscribeInfoCollectedTime < RECENTLY_DURATION) { + return true; + } + return false; + } + + public boolean canCurrentServerOwnTheLock() { + boolean currentOwnerIsMe = subInfoCollectLockOwner==null? true:SystemConfig.LOCAL_IP + .equals(subInfoCollectLockOwner); + + if (currentOwnerIsMe) { + return true; + } + if (System.currentTimeMillis() - this.subInfoCollectLockExpireTime > LOCK_EXPIRE_DURATION) { + return true; + } + + return false; + } + + public String currentServer(){ + return SystemConfig.LOCAL_IP; + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/app/ApplicationPublishRecord.java b/config/src/main/java/com/alibaba/nacos/config/server/model/app/ApplicationPublishRecord.java new file mode 100644 index 00000000000..fbb13bdef2e --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/app/ApplicationPublishRecord.java @@ -0,0 +1,49 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model.app; +/** + * Application Publish Record + * @author Nacos + * + */ +public class ApplicationPublishRecord { + + private String appName; + private GroupKey configInfo; + + public ApplicationPublishRecord(String appName, String dataId, String groupId){ + this.appName = appName; + this.configInfo = new GroupKey(dataId, groupId); + } + + public String getAppName() { + return appName; + } + + public void setAppName(String appName) { + this.appName = appName; + } + + public GroupKey getConfigInfo() { + return configInfo; + } + + public void setConfigInfo(GroupKey configInfo) { + this.configInfo = configInfo; + } + + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/app/GroupKey.java b/config/src/main/java/com/alibaba/nacos/config/server/model/app/GroupKey.java new file mode 100644 index 00000000000..51b5cf512cd --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/app/GroupKey.java @@ -0,0 +1,69 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model.app; + +import com.alibaba.nacos.config.server.utils.GroupKey2; + +/** + * config key util + * + * @author Nacos + * + */ +public class GroupKey extends GroupKey2 { + + private String dataId; + private String group; + + public GroupKey(String dataId, String group) { + this.dataId = dataId; + this.group = group; + } + + public GroupKey(String groupKeyString) { + String[] groupKeys = parseKey(groupKeyString); + this.dataId = groupKeys[0]; + this.group = groupKeys[1]; + } + + public String getDataId() { + return dataId; + } + + public void setDataId(String dataId) { + this.dataId = dataId; + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + public String toString() { + return dataId + "+" + group; + } + + + public String getGroupkeyString() { + return getKey(dataId, group); + } + + //TODO : equal as we use Set + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/app/MonitorInfo.java b/config/src/main/java/com/alibaba/nacos/config/server/model/app/MonitorInfo.java new file mode 100644 index 00000000000..9b5a7cfcca6 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/app/MonitorInfo.java @@ -0,0 +1,133 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model.app; + +/** + * Created by qingliang on 2017/7/20. + * + * @author Nacos + */ +public class MonitorInfo { + /** 可使用内存. */ + private long totalMemory; + /** 剩余内存. */ + private long freeMemory; + /** 最大可使用内存. */ + private volatile long maxMemory; + /** cpu使用率. */ + private double cpuRatio; + /** 系统负载. */ + private double load; + /** ygc次数 */ + private int ygc; + /** ygc时间 */ + private double ygct; + /** fgc次数 */ + private int fgc; + /** fgc时间 */ + private double fgct; + /** gc时间 */ + private double gct; + + + public long getFreeMemory() { + return freeMemory; + } + public void setFreeMemory(long freeMemory) { + this.freeMemory = freeMemory; + } + public long getMaxMemory() { + return maxMemory; + } + public void setMaxMemory(long maxMemory) { + this.maxMemory = maxMemory; + } + public long getTotalMemory() { + return totalMemory; + } + public void setTotalMemory(long totalMemory) { + this.totalMemory = totalMemory; + } + public double getCpuRatio() { + return cpuRatio; + } + public void setCpuRatio(int cpuRatio) { + this.cpuRatio = cpuRatio; + } + public double getLoad() { + return load; + } + public void setLoad(int load) { + this.load = load; + } + + public int getYgc() { + return ygc; + } + + public void setYgc(int ygc) { + this.ygc = ygc; + } + + public double getYgct() { + return ygct; + } + + public void setYgct(int ygct) { + this.ygct = ygct; + } + + public int getFgc() { + return fgc; + } + + public void setFgc(int fgc) { + this.fgc = fgc; + } + + public double getFgct() { + return fgct; + } + + public void setFgct(int fgct) { + this.fgct = fgct; + } + + public double getGct() { + return gct; + } + + public void setGct(int gct) { + this.gct = gct; + } + + @Override + public String toString() { + return "MonitorInfo{" + + "totalMemory=" + totalMemory + + ", freeMemory=" + freeMemory + + ", maxMemory=" + maxMemory + + ", cpuRatio=" + cpuRatio + + ", load=" + load + + ", ygc=" + ygc + + ", ygct=" + ygct + + ", fgc=" + fgc + + ", fgct=" + fgct + + ", gct=" + gct + + '}'; + } +} + diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/capacity/Capacity.java b/config/src/main/java/com/alibaba/nacos/config/server/model/capacity/Capacity.java new file mode 100644 index 00000000000..7afacbf85ad --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/capacity/Capacity.java @@ -0,0 +1,113 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model.capacity; + +import java.sql.Timestamp; + +/** + * Capacity + * @author hexu.hxy + * @date 2018/3/13 + */ +public class Capacity { + private Long id; + private Integer quota; + private Integer usage; + private Integer maxSize; + private Integer maxAggrCount; + private Integer maxAggrSize; + private Timestamp gmtCreate; + private Timestamp gmtModified; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Integer getQuota() { + return quota; + } + + public void setQuota(Integer quota) { + this.quota = quota; + } + + public Integer getUsage() { + return usage; + } + + public void setUsage(Integer usage) { + this.usage = usage; + } + + public Integer getMaxSize() { + return maxSize; + } + + public void setMaxSize(Integer maxSize) { + this.maxSize = maxSize; + } + + public Integer getMaxAggrCount() { + return maxAggrCount; + } + + public void setMaxAggrCount(Integer maxAggrCount) { + this.maxAggrCount = maxAggrCount; + } + + public Integer getMaxAggrSize() { + return maxAggrSize; + } + + public void setMaxAggrSize(Integer maxAggrSize) { + this.maxAggrSize = maxAggrSize; + } + + public Timestamp getGmtCreate() { + if (gmtCreate == null) { + return null; + } + return new Timestamp(gmtCreate.getTime()); + } + + public void setGmtCreate(Timestamp gmtCreate) { + if (gmtCreate == null) { + this.gmtCreate = null; + } else { + this.gmtCreate = new Timestamp(gmtCreate.getTime()); + } + + } + + public Timestamp getGmtModified() { + if (gmtModified == null) { + return null; + } + return new Timestamp(gmtModified.getTime()); + } + + public void setGmtModified(Timestamp gmtModified) { + if (gmtModified == null) { + this.gmtModified = null; + } else { + this.gmtModified = new Timestamp(gmtModified.getTime()); + } + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/capacity/GroupCapacity.java b/config/src/main/java/com/alibaba/nacos/config/server/model/capacity/GroupCapacity.java new file mode 100644 index 00000000000..3a8d612ee9f --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/capacity/GroupCapacity.java @@ -0,0 +1,33 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model.capacity; + +/** + * Group Capacity + * @author hexu.hxy + * @date 2018/3/13 + */ +public class GroupCapacity extends Capacity { + private String group; + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/capacity/TenantCapacity.java b/config/src/main/java/com/alibaba/nacos/config/server/model/capacity/TenantCapacity.java new file mode 100644 index 00000000000..9f85382eb35 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/capacity/TenantCapacity.java @@ -0,0 +1,33 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.model.capacity; + +/** + * Tenant Capacity + * @author hexu.hxy + * @date 2018/3/13 + */ +public class TenantCapacity extends Capacity { + private String tenant; + + public String getTenant() { + return tenant; + } + + public void setTenant(String tenant) { + this.tenant = tenant; + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/monitor/MemoryMonitor.java b/config/src/main/java/com/alibaba/nacos/config/server/monitor/MemoryMonitor.java new file mode 100755 index 00000000000..b059bf27392 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/monitor/MemoryMonitor.java @@ -0,0 +1,92 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.monitor; + +import static com.alibaba.nacos.config.server.utils.LogUtil.memoryLog; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.alibaba.nacos.config.server.service.ClientTrackService; +import com.alibaba.nacos.config.server.service.ConfigService; +import com.alibaba.nacos.config.server.service.TimerTaskService; +import com.alibaba.nacos.config.server.service.notify.AsyncNotifyService; + +/** + * Memory monitor + * + * @author Nacos + * + */ +@Service +public class MemoryMonitor { + @Autowired + public MemoryMonitor(AsyncNotifyService notifySingleService) { + + TimerTaskService.scheduleWithFixedDelay(new PrintMemoryTask(), DELAY_SECONDS, + DELAY_SECONDS, TimeUnit.SECONDS); + + TimerTaskService.scheduleWithFixedDelay(new PrintGetConfigResponeTask(), DELAY_SECONDS, + DELAY_SECONDS, TimeUnit.SECONDS); + + TimerTaskService.scheduleWithFixedDelay(new NotifyTaskQueueMonitorTask(notifySingleService), DELAY_SECONDS, + DELAY_SECONDS, TimeUnit.SECONDS); + } + + + static final long DELAY_SECONDS = 10; +} + +class PrintGetConfigResponeTask implements Runnable{ + @Override + public void run() { + memoryLog.info(ResponseMonitor.getStringForPrint()); + } +} + +class PrintMemoryTask implements Runnable { + @Override + public void run() { + int groupCount = ConfigService.groupCount(); + int subClientCount = ClientTrackService.subscribeClientCount(); + long subCount = ClientTrackService.subscriberCount(); + memoryLog.info("groupCount={}, subscriberClientCount={}, subscriberCount={}", + new Object[] { groupCount, subClientCount, subCount }); + } +} + + +class NotifyTaskQueueMonitorTask implements Runnable { + final private AsyncNotifyService notifySingleService; + + NotifyTaskQueueMonitorTask(AsyncNotifyService notifySingleService) { + this.notifySingleService = notifySingleService; + } + + @Override + public void run() { + + memoryLog.info("notifySingleServiceThreadPool-{}, toNotifyTaskSize={}", + new Object[] {((ScheduledThreadPoolExecutor)notifySingleService.getExecutor()).getClass().getName(), ((ScheduledThreadPoolExecutor)notifySingleService.getExecutor()).getQueue().size() }); + +// for(Map.Entry entry: notifySingleService.getExecutors().entrySet()) { +// ThreadPoolExecutor pool = (ThreadPoolExecutor) entry.getValue(); +// String target = entry.getKey(); +// memoryLog.info("notifySingleServiceThreadPool-{}, toNotifyTaskSize={}", +// new Object[] { target, pool.getQueue().size() }); +// } + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/monitor/ResponseMonitor.java b/config/src/main/java/com/alibaba/nacos/config/server/monitor/ResponseMonitor.java new file mode 100644 index 00000000000..ea98ae7b0db --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/monitor/ResponseMonitor.java @@ -0,0 +1,97 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.monitor; + +import java.text.DecimalFormat; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Response Monitory + * + * @author Nacos + * + */ +public class ResponseMonitor { + static AtomicLong[] getConfigCountDetail = new AtomicLong[8]; + static AtomicLong getConfigCount = new AtomicLong(); + private static final int MS_50 = 50; + private static final int MS_100 = 100; + private static final int MS_200 = 200; + private static final int MS_500 = 500; + private static final int MS_1000 = 1000; + private static final int MS_2000 = 2000; + private static final int MS_3000 = 3000; + + static{ + refresh(); + } + + public static void refresh(){ + for(int i = 0; i< getConfigCountDetail.length;i++){ + getConfigCountDetail[i] = new AtomicLong(); + } + } + + public static void addConfigTime(long time){ + getConfigCount.incrementAndGet(); + if(time < MS_50){ + getConfigCountDetail[0].incrementAndGet(); + } else if(time < MS_100) { + getConfigCountDetail[1].incrementAndGet(); + } else if (time < MS_200){ + getConfigCountDetail[2].incrementAndGet(); + } else if(time < MS_500){ + getConfigCountDetail[3].incrementAndGet(); + } else if(time < MS_1000){ + getConfigCountDetail[4].incrementAndGet(); + } else if(time < MS_2000){ + getConfigCountDetail[5].incrementAndGet(); + } else if(time < MS_3000){ + getConfigCountDetail[6].incrementAndGet(); + } else { + getConfigCountDetail[7].incrementAndGet(); + } + } + + public static String getStringForPrint(){ + DecimalFormat df = new DecimalFormat("##.0"); + StringBuilder s = new StringBuilder("getConfig monitor:\r\n"); + s.append("0-50ms:" + df.format(getConfigCountDetail[0].getAndSet(0)*100/ getConfigCount.get())).append("%\r\n"); + s.append("100-200ms:" + df.format(getConfigCountDetail[2].getAndSet(0)*100/ getConfigCount.get())).append("%\r\n"); + s.append("200-500ms:" + df.format(getConfigCountDetail[3].getAndSet(0)*100/ getConfigCount.get())).append("%\r\n"); + s.append("500-1000ms:" + df.format(getConfigCountDetail[4].getAndSet(0)*100/ getConfigCount.get())).append("%\r\n"); + s.append("1000-2000ms:" + df.format(getConfigCountDetail[5].getAndSet(0)*100/ getConfigCount.get())).append("%\r\n"); + s.append("2000-3000ms:" + df.format(getConfigCountDetail[6].getAndSet(0)*100/ getConfigCount.get())).append("%\r\n"); + s.append("3000以上ms:" + df.format(getConfigCountDetail[7].getAndSet(0)*100/ getConfigCount.getAndSet(0))).append("%\r\n"); + return s.toString(); + } + + public static void main(String[] args) { + ResponseMonitor.addConfigTime(10); + ResponseMonitor.addConfigTime(10); + ResponseMonitor.addConfigTime(10); + ResponseMonitor.addConfigTime(10); + ResponseMonitor.addConfigTime(100); + ResponseMonitor.addConfigTime(150); + ResponseMonitor.addConfigTime(250); + ResponseMonitor.addConfigTime(350); + ResponseMonitor.addConfigTime(750); + ResponseMonitor.addConfigTime(15000); + System.out.println(ResponseMonitor.getStringForPrint()); + System.out.println(ResponseMonitor.getStringForPrint()); + + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/AggrWhitelist.java b/config/src/main/java/com/alibaba/nacos/config/server/service/AggrWhitelist.java new file mode 100644 index 00000000000..7b7f65ead0c --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/AggrWhitelist.java @@ -0,0 +1,97 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service; + +import static com.alibaba.nacos.config.server.utils.LogUtil.defaultLog; +import static com.alibaba.nacos.config.server.utils.LogUtil.fatalLog; + +import java.io.StringReader; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Pattern; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; +import org.springframework.stereotype.Service; + +import com.alibaba.nacos.config.server.utils.RegexParser; + + +/** + * 聚合数据白名单。 + * @author Nacos + */ +@Service +public class AggrWhitelist { + + /** + * 判断指定的dataId是否在聚合dataId白名单。 + */ + static public boolean isAggrDataId(String dataId) { + if (null == dataId) { + throw new IllegalArgumentException(); + } + + for (Pattern pattern : AGGR_DATAID_WHITELIST.get()) { + if (pattern.matcher(dataId).matches()) { + return true; + } + } + return false; + } + + /** + * 传入内容,重新加载聚合白名单 + */ + static public void load(String content) { + if (StringUtils.isBlank(content)) { + fatalLog.error("aggr dataId whitelist is blank."); + return; + } + defaultLog.warn("[aggr-dataIds] {}", content); + + try { + List lines = IOUtils.readLines(new StringReader(content)); + compile(lines); + } catch (Exception ioe) { + defaultLog.error("failed to load aggr whitelist, " + ioe.toString(), ioe); + } + } + + static void compile(List whitelist) { + List list = new ArrayList(whitelist.size()); + + for (String line : whitelist) { + if (!StringUtils.isBlank(line)) { + String regex = RegexParser.regexFormat(line.trim()); + list.add(Pattern.compile(regex)); + } + } + AGGR_DATAID_WHITELIST.set(list); + } + + static public List getWhiteList() { + return AGGR_DATAID_WHITELIST.get(); + } + + // ======================= + + static public final String AGGRIDS_METADATA = "com.alibaba.nacos.metadata.aggrIDs"; + + static final AtomicReference> AGGR_DATAID_WHITELIST = new AtomicReference>( + new ArrayList()); +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/BasicDataSourceServiceImpl.java b/config/src/main/java/com/alibaba/nacos/config/server/service/BasicDataSourceServiceImpl.java new file mode 100644 index 00000000000..56025a2e1ac --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/BasicDataSourceServiceImpl.java @@ -0,0 +1,331 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service; + +import static com.alibaba.nacos.config.server.service.PersistService.CONFIG_INFO4BETA_ROW_MAPPER; +import static com.alibaba.nacos.config.server.utils.LogUtil.defaultLog; +import static com.alibaba.nacos.config.server.utils.LogUtil.fatalLog; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.annotation.PostConstruct; +import javax.sql.DataSource; + +import org.apache.commons.dbcp.BasicDataSource; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.math.NumberUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.CannotGetJdbcConnectionException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.stereotype.Service; +import org.springframework.transaction.support.TransactionTemplate; + +import com.alibaba.nacos.config.server.utils.PropertyUtil; + +/** + * Base data source + * @author Nacos + * + */ +@Service("basicDataSourceService") +public class BasicDataSourceServiceImpl implements DataSourceService { + private static final String JDBC_DRIVER_NAME = "com.mysql.jdbc.Driver"; + + /** + * JDBC执行超时时间, 单位秒 + */ + private int queryTimeout = 3; + + private static final int TRANSACTION_QUERY_TIMEOUT = 5; + + private static final String DB_LOAD_ERROR_MSG = "[db-load-error]load jdbc.properties error"; + + private List dataSourceList = new ArrayList(); + private JdbcTemplate jt; + private DataSourceTransactionManager tm; + private TransactionTemplate tjt; + + private JdbcTemplate testMasterJT; + private JdbcTemplate testMasterWritableJT; + + volatile private List testJTList; + volatile private List isHealthList; + private volatile int masterIndex; + private static Pattern ipPattern = Pattern.compile("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"); + + @Autowired + private Environment env; + + @PostConstruct + public void init() { + queryTimeout = NumberUtils + .toInt(System.getProperty("QUERYTIMEOUT"), 3); + jt = new JdbcTemplate(); + /** + * 设置最大记录数,防止内存膨胀 + */ + jt.setMaxRows(50000); + jt.setQueryTimeout(queryTimeout); + + testMasterJT = new JdbcTemplate(); + testMasterJT.setQueryTimeout(queryTimeout); + + testMasterWritableJT = new JdbcTemplate(); + /** + * 防止login接口因为主库不可用而rt太长 + */ + testMasterWritableJT.setQueryTimeout(1); + /** + * 数据库健康检测 + */ + testJTList = new ArrayList(); + isHealthList = new ArrayList(); + + tm = new DataSourceTransactionManager(); + tjt = new TransactionTemplate(tm); + /** + * 事务的超时时间需要与普通操作区分开 + */ + tjt.setTimeout(TRANSACTION_QUERY_TIMEOUT); + if (!PropertyUtil.isStandaloneMode()) { + try { + reload(); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(DB_LOAD_ERROR_MSG); + } + + TimerTaskService.scheduleWithFixedDelay(new SelectMasterTask(), 10, 10, + TimeUnit.SECONDS); + TimerTaskService.scheduleWithFixedDelay(new CheckDBHealthTask(), 10, 10, + TimeUnit.SECONDS); + } + } + + public synchronized void reload() throws IOException { + List dblist = new ArrayList(); + try { + String val = null; + val = env.getProperty("db.num"); + if (null == val) { + throw new IllegalArgumentException("db.num is null"); + } + int dbNum = Integer.parseInt(val.trim()); + + for (int i = 0; i < dbNum; i++) { + BasicDataSource ds = new BasicDataSource(); + ds.setDriverClassName(JDBC_DRIVER_NAME); + + val = env.getProperty("db.url." + i); + if (null == val) { + fatalLog.error("db.url." + i + " is null"); + throw new IllegalArgumentException(); + } + ds.setUrl(val.trim()); + + val = env.getProperty("db.user"); + if (null == val) { + fatalLog.error("db.user is null"); + throw new IllegalArgumentException(); + } + ds.setUsername(val.trim()); + + val = env.getProperty("db.password"); + if (null == val) { + fatalLog.error("db.password is null"); + throw new IllegalArgumentException(); + } + ds.setPassword(val.trim()); + + val = env.getProperty("db.initialSize"); + ds.setInitialSize(Integer.parseInt(defaultIfNull(val, "10"))); + + val = env.getProperty("db.maxActive"); + ds.setMaxActive(Integer.parseInt(defaultIfNull(val, "20"))); + + val = env.getProperty("db.maxIdle"); + ds.setMaxIdle(Integer.parseInt(defaultIfNull(val, "50"))); + + ds.setMaxWait(3000L); + ds.setPoolPreparedStatements(true); + + // 每10分钟检查一遍连接池 + ds.setTimeBetweenEvictionRunsMillis(TimeUnit.MINUTES + .toMillis(10L)); + ds.setTestWhileIdle(true); + ds.setValidationQuery("SELECT 1 FROM dual"); + + dblist.add(ds); + + JdbcTemplate jdbcTemplate = new JdbcTemplate(); + jdbcTemplate.setQueryTimeout(queryTimeout); + jdbcTemplate.setDataSource(ds); + + testJTList.add(jdbcTemplate); + isHealthList.add(Boolean.TRUE); + } + + if (dblist == null || dblist.size() == 0) { + throw new RuntimeException("no datasource available"); + } + + dataSourceList = dblist; + new SelectMasterTask().run(); + new CheckDBHealthTask().run(); + } catch (RuntimeException e) { + fatalLog.error(DB_LOAD_ERROR_MSG, e); + throw new IOException(e); + } finally { + } + } + + public boolean checkMasterWritable() { + + testMasterWritableJT.setDataSource(jt.getDataSource()); + /** + * 防止login接口因为主库不可用而rt太长 + */ + testMasterWritableJT.setQueryTimeout(1); + String sql = " select @@read_only "; + + try { + Integer result = testMasterWritableJT.queryForObject(sql, Integer.class); + if (result == null) { + return false; + } else { + return result.intValue() == 0 ? true : false; + } + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + return false; + } + + } + + public JdbcTemplate getJdbcTemplate() { + return this.jt; + } + + public TransactionTemplate getTransactionTemplate() { + return this.tjt; + } + + public String getCurrentDBUrl() { + DataSource ds = this.jt.getDataSource(); + if (ds == null) { + return StringUtils.EMPTY; + } + BasicDataSource bds = (BasicDataSource) ds; + return bds.getUrl(); + } + + public String getHealth() { + for (int i = 0 ; i < isHealthList.size(); i++) { + if (!isHealthList.get(i)) { + if (i == masterIndex) { + /** + * 主库不健康 + */ + return "DOWN:" + getIpFromUrl(dataSourceList.get(i).getUrl()); + } else { + /** + * 从库不健康 + */ + return "WARN:" + getIpFromUrl(dataSourceList.get(i).getUrl()); + } + } + } + + return "UP"; + } + + private String getIpFromUrl(String url) { + + Matcher m = ipPattern.matcher(url); + if (m.find()) { + return m.group(); + } + + return ""; + } + + static String defaultIfNull(String value, String defaultValue) { + return null == value ? defaultValue : value; + } + + class SelectMasterTask implements Runnable { + public void run() { + defaultLog.info("check master db."); + boolean isFound = false; + + int index = -1; + for (BasicDataSource ds : dataSourceList) { + index++; + testMasterJT.setDataSource(ds); + testMasterJT.setQueryTimeout(queryTimeout); + try { + testMasterJT + .update("delete from config_info where data_id='com.alibaba.nacos.testMasterDB'"); + if (jt.getDataSource() != ds) { + fatalLog.warn("[master-db] {}", ds.getUrl()); + } + jt.setDataSource(ds); + tm.setDataSource(ds); + isFound = true; + masterIndex = index; + break; + } catch (DataAccessException e) { // read only + e.printStackTrace(); // TODO remove + } + } + + if (!isFound) { + fatalLog.error("[master-db] master db not found."); + } + } + } + + @SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") + class CheckDBHealthTask implements Runnable { + public void run() { + defaultLog.info("check db health."); + String sql = "SELECT * FROM config_info_beta WHERE id = 1"; + + for (int i = 0; i < testJTList.size(); i++) { + JdbcTemplate jdbcTemplate = testJTList.get(i); + try { + jdbcTemplate.query(sql, CONFIG_INFO4BETA_ROW_MAPPER); + isHealthList.set(i, Boolean.TRUE); + } catch (DataAccessException e) { + if (i == masterIndex) { + fatalLog.error("[db-error] master db {} down.", getIpFromUrl(dataSourceList.get(i).getUrl())); + } else { + fatalLog.error("[db-error] slave db {} down.", getIpFromUrl(dataSourceList.get(i).getUrl())); + } + isHealthList.set(i, Boolean.FALSE); + } + } + } + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/ClientIpWhiteList.java b/config/src/main/java/com/alibaba/nacos/config/server/service/ClientIpWhiteList.java new file mode 100644 index 00000000000..e3f011205cd --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/ClientIpWhiteList.java @@ -0,0 +1,88 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service; + +import static com.alibaba.nacos.config.server.utils.LogUtil.defaultLog; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.commons.lang.StringUtils; +import org.springframework.stereotype.Service; + +import com.alibaba.nacos.config.server.model.ACLInfo; +import com.alibaba.nacos.config.server.utils.JSONUtils; +/** + * Client ip whitelist + * @author Nacos + * + */ +@Service +public class ClientIpWhiteList { + + /** + * 判断指定的ip在白名单中 + */ + static public boolean isLegalClient(String clientIp) { + if (StringUtils.isBlank(clientIp)) { + throw new IllegalArgumentException(); + } + clientIp = clientIp.trim(); + if (CLIENT_IP_WHITELIST.get().contains(clientIp)) { + return true; + } + return false; + } + + /** + * whether start client ip whitelist + * + * @return true: enable ; false disable + */ + static public boolean isEnableWhitelist() { + return isOpen; + } + + /** + * 传入内容,重新加载客户端ip白名单 + */ + static public void load(String content) { + if (StringUtils.isBlank(content)) { + defaultLog.warn("clientIpWhiteList is blank.close whitelist."); + isOpen = false; + CLIENT_IP_WHITELIST.get().clear(); + return; + } + defaultLog.warn("[clientIpWhiteList] {}", content); + try { + ACLInfo acl=(ACLInfo)JSONUtils.deserializeObject(content, ACLInfo.class); + isOpen = acl.getIsOpen(); + CLIENT_IP_WHITELIST.set(acl.getIps()); + } catch (Exception ioe) { + defaultLog.error( + "failed to load clientIpWhiteList, " + ioe.toString(), ioe); + } + } + + // ======================= + + static public final String CLIENT_IP_WHITELIST_METADATA = "com.alibaba.nacos.metadata.clientIpWhitelist"; + + static final AtomicReference> CLIENT_IP_WHITELIST = new AtomicReference>( + new ArrayList()); + static Boolean isOpen = false; +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/ClientTrackService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/ClientTrackService.java new file mode 100755 index 00000000000..448e6de4750 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/ClientTrackService.java @@ -0,0 +1,184 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import com.alibaba.nacos.config.server.model.SubscriberStatus; + + +/** + * 跟踪客户端md5的服务。 一段时间没有比较md5后,就删除IP对应的记录。 + * @author Nacos + */ +public class ClientTrackService { + /** + * 跟踪客户端md5. + */ + static public void trackClientMd5(String ip, Map clientMd5Map) { + ClientRecord record = getClientRecord(ip); + record.lastTime = System.currentTimeMillis(); + record.groupKey2md5Map.putAll(clientMd5Map); + } + + static public void trackClientMd5(String ip, Map clientMd5Map, Map clientlastPollingTSMap) { + ClientRecord record = getClientRecord(ip); + record.lastTime = System.currentTimeMillis(); + record.groupKey2md5Map.putAll(clientMd5Map); + record.groupKey2pollingTsMap.putAll(clientlastPollingTSMap); + } + + static public void trackClientMd5(String ip, String groupKey, String clientMd5) { + ClientRecord record = getClientRecord(ip); + record.lastTime = System.currentTimeMillis(); + record.groupKey2md5Map.put(groupKey, clientMd5); + record.groupKey2pollingTsMap.put(groupKey, record.lastTime); + } + + + /** + * 返回订阅者客户端个数 + */ + static public int subscribeClientCount() { + return clientRecords.size(); + } + + /** + * 返回所有订阅者个数 + */ + static public long subscriberCount() { + long count = 0; + for (ClientRecord record : clientRecords.values()) { + count += record.groupKey2md5Map.size(); + } + return count; + } + + /** + * groupkey -> SubscriberStatus + */ + static public Map listSubStatus(String ip){ + Map status = new HashMap(100); + + ClientRecord record = getClientRecord(ip); + if(record == null) { + return status; + } + + for (Map.Entry entry : record.groupKey2md5Map.entrySet()) { + String groupKey = entry.getKey(); + String clientMd5 = entry.getValue(); + long lastPollingTs = record.groupKey2pollingTsMap.get(groupKey); + boolean isUpdate = ConfigService.isUptodate(groupKey, clientMd5); + + status.put(groupKey, new SubscriberStatus(groupKey, isUpdate, clientMd5, lastPollingTs)); + } + + return status; + } + + /** + * ip -> SubscriberStatus + */ + static public Map listSubsByGroup(String groupKey) { + Map subs = new HashMap(100); + + for (ClientRecord clientRec : clientRecords.values()) { + String clientMd5 = clientRec.groupKey2md5Map.get(groupKey); + Long lastPollingTs = clientRec.groupKey2pollingTsMap.get(groupKey); + + if (null != clientMd5 && lastPollingTs != null) { + Boolean isUpdate = ConfigService.isUptodate(groupKey, clientMd5); + subs.put(clientRec.ip, new SubscriberStatus(groupKey, isUpdate, clientMd5, lastPollingTs)); + } + + } + return subs; + } + + /** + * 指定订阅者IP,查找数据是否最新。 groupKey -> isUptodate + */ + static public Map isClientUptodate(String ip) { + Map result = new HashMap(100); + for (Map.Entry entry : getClientRecord(ip).groupKey2md5Map.entrySet()) { + String groupKey = entry.getKey(); + String clientMd5 = entry.getValue(); + Boolean isuptodate = ConfigService.isUptodate(groupKey, clientMd5); + result.put(groupKey, isuptodate); + } + return result; + } + + /** + * 指定groupKey,查找所有订阅者以及数据是否最新。 IP -> isUptodate + */ + static public Map listSubscriberByGroup(String groupKey) { + Map subs = new HashMap(100); + + for (ClientRecord clientRec : clientRecords.values()) { + String clientMd5 = clientRec.groupKey2md5Map.get(groupKey); + if (null != clientMd5) { + Boolean isuptodate = ConfigService.isUptodate(groupKey, clientMd5); + subs.put(clientRec.ip, isuptodate); + } + } + return subs; + } + + /** + * 找到指定clientIp对应的记录。 + */ + static private ClientRecord getClientRecord(String clientIp) { + ClientRecord record = clientRecords.get(clientIp); + if (null != record) { + return record; + } + clientRecords.putIfAbsent(clientIp, new ClientRecord(clientIp)); + return clientRecords.get(clientIp); + } + + static public void refreshClientRecord(){ + clientRecords = new ConcurrentHashMap(50); + } + + /** + * 所有客户端记录。遍历 >> 新增/删除 + */ + static volatile ConcurrentMap clientRecords = new ConcurrentHashMap(); +} + +/** + * 保存客户端拉数据的记录。 + */ +class ClientRecord { + final String ip; + volatile long lastTime; + final ConcurrentMap groupKey2md5Map; + final ConcurrentMap groupKey2pollingTsMap; + + + ClientRecord(String clientIp) { + ip = clientIp; + groupKey2md5Map = new ConcurrentHashMap(20, 0.75f, 1); + groupKey2pollingTsMap = new ConcurrentHashMap(20, 0.75f, 1); + } +} + + diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigDataChangeEvent.java b/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigDataChangeEvent.java new file mode 100644 index 00000000000..ed9030e37b6 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigDataChangeEvent.java @@ -0,0 +1,69 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service; + +import org.apache.commons.lang.StringUtils; + +import com.alibaba.nacos.config.server.utils.event.EventDispatcher.Event; + + +/** + * 指数据发布事件。 + * @author Nacos + */ +public class ConfigDataChangeEvent implements Event { + + final public boolean isBeta; + final public String dataId; + final public String group; + final public String tenant; + final public String tag; + final public long lastModifiedTs; + + public ConfigDataChangeEvent(String dataId, String group, long gmtModified) { + this(false, dataId, group, gmtModified); + } + + public ConfigDataChangeEvent(boolean isBeta, String dataId, String group, String tenant, long gmtModified) { + if (null == dataId || null == group) { + throw new IllegalArgumentException(); + } + this.isBeta = isBeta; + this.dataId = dataId; + this.group = group; + this.tenant = tenant; + this.tag = null; + this.lastModifiedTs = gmtModified; + } + + public ConfigDataChangeEvent(boolean isBeta, String dataId, String group, long gmtModified) { + this(isBeta, dataId, group, StringUtils.EMPTY, gmtModified); + } + + public ConfigDataChangeEvent(boolean isBeta, String dataId, String group, String tenant, String tag, + long gmtModified) { + if (null == dataId || null == group) { + throw new IllegalArgumentException(); + } + this.isBeta = isBeta; + this.dataId = dataId; + this.group = group; + this.tenant = tenant; + this.tag = tag; + this.lastModifiedTs = gmtModified; + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigService.java new file mode 100644 index 00000000000..d1e01a8ee64 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigService.java @@ -0,0 +1,587 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service; + +import static com.alibaba.nacos.config.server.utils.LogUtil.defaultLog; +import static com.alibaba.nacos.config.server.utils.LogUtil.dumpLog; +import static com.alibaba.nacos.config.server.utils.LogUtil.fatalLog; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +import com.alibaba.nacos.config.server.model.ConfigInfoBase; +import com.alibaba.nacos.config.server.utils.PropertyUtil; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.model.CacheItem; +import com.alibaba.nacos.config.server.utils.GroupKey; +import com.alibaba.nacos.config.server.utils.GroupKey2; +import com.alibaba.nacos.config.server.utils.MD5; +import com.alibaba.nacos.config.server.utils.event.EventDispatcher; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * config service + * @author Nacos + * + */ +public class ConfigService { + + @Autowired + private static PersistService persistService; + + static public int groupCount() { + return CACHE.size(); + } + + static public boolean hasGroupKey(String groupKey) { + return CACHE.containsKey(groupKey); + } + + /** + * 保存配置文件,并缓存md5. + */ + static public boolean dump(String dataId, String group, String tenant, String content, long lastModifiedTs) { + String groupKey = GroupKey2.getKey(dataId, group, tenant); + makeSure(groupKey); + final int lockResult = tryWriteLock(groupKey); + assert (lockResult != 0); + + if (lockResult < 0) { + dumpLog.warn("[dump-error] write lock failed. {}", groupKey); + return false; + } + + try { + final String md5 = MD5.getInstance().getMD5String(content); + if (md5.equals(ConfigService.getContentMd5(groupKey))) { + dumpLog.warn( + "[dump-ignore] ignore to save cache file. groupKey={}, md5={}, lastModifiedOld={}, lastModifiedNew={}", + new Object[] { groupKey, md5, ConfigService.getLastModifiedTs(groupKey), lastModifiedTs }); + } else if (!PropertyUtil.isStandaloneMode()) { + DiskUtil.saveToDisk(dataId, group, tenant, content); + } + updateMd5(groupKey, md5, lastModifiedTs); + return true; + } catch (IOException ioe) { + dumpLog.error("[dump-exception] save disk error. " + groupKey + ", " + ioe.toString(), ioe); + if (ioe.getMessage() != null) { + String errMsg = ioe.getMessage(); + if (NO_SPACE_CN.equals(errMsg) || NO_SPACE_EN.equals(errMsg) || errMsg.contains(DISK_QUATA_CN) + || errMsg.contains(DISK_QUATA_EN)) { + // 磁盘写满保护代码 + fatalLog.error("磁盘满自杀退出", ioe); + System.exit(0); + } + } + return false; + } finally { + releaseWriteLock(groupKey); + } + } + + /** + * 保存配置文件,并缓存md5. + */ + static public boolean dumpBeta(String dataId, String group, String tenant, String content, long lastModifiedTs, String betaIps) { + final String groupKey = GroupKey2.getKey(dataId, group, tenant); + + makeSure(groupKey); + final int lockResult = tryWriteLock(groupKey); + assert (lockResult != 0); + + if (lockResult < 0) { + dumpLog.warn("[dump-beta-error] write lock failed. {}", groupKey); + return false; + } + + try { + final String md5 = MD5.getInstance().getMD5String(content); + if(md5.equals(ConfigService.getContentBetaMd5(groupKey))) { + dumpLog.warn("[dump-beta-ignore] ignore to save cache file. groupKey={}, md5={}, lastModifiedOld={}, lastModifiedNew={}", new Object[]{groupKey, md5, ConfigService.getLastModifiedTs(groupKey), lastModifiedTs}); + } else if (!PropertyUtil.isStandaloneMode()) { + DiskUtil.saveBetaToDisk(dataId, group, tenant, content); + } + String[] betaIpsArr = betaIps.split(","); + + updateBetaMd5(groupKey, md5, Arrays.asList(betaIpsArr), lastModifiedTs); + return true; + } catch (IOException ioe) { + dumpLog.error("[dump-beta-exception] save disk error. " + groupKey + ", " + ioe.toString(), + ioe); + return false; + } finally { + releaseWriteLock(groupKey); + } + } + + /** + * 保存配置文件,并缓存md5. + */ + static public boolean dumpTag(String dataId, String group, String tenant, String tag, String content, long lastModifiedTs) { + final String groupKey = GroupKey2.getKey(dataId, group, tenant); + + makeSure(groupKey); + final int lockResult = tryWriteLock(groupKey); + assert (lockResult != 0); + + if (lockResult < 0) { + dumpLog.warn("[dump-tag-error] write lock failed. {}", groupKey); + return false; + } + + try { + final String md5 = MD5.getInstance().getMD5String(content); + if(md5.equals(ConfigService.getContentTagMd5(groupKey,tag))) { + dumpLog.warn("[dump-tag-ignore] ignore to save cache file. groupKey={}, md5={}, lastModifiedOld={}, lastModifiedNew={}", new Object[]{groupKey, md5, ConfigService.getLastModifiedTs(groupKey), lastModifiedTs}); + } else if (!PropertyUtil.isStandaloneMode()) { + DiskUtil.saveTagToDisk(dataId, group, tenant, tag, content); + } + + updateTagMd5(groupKey, tag, md5, lastModifiedTs); + return true; + } catch (IOException ioe) { + dumpLog.error("[dump-tag-exception] save disk error. " + groupKey + ", " + ioe.toString(), + ioe); + return false; + } finally { + releaseWriteLock(groupKey); + } + } + + /** + * 保存配置文件,并缓存md5. + */ + static public boolean dumpChange(String dataId, String group, String tenant, String content, long lastModifiedTs) { + final String groupKey = GroupKey2.getKey(dataId, group, tenant); + + makeSure(groupKey); + final int lockResult = tryWriteLock(groupKey); + assert (lockResult != 0); + + if (lockResult < 0) { + dumpLog.warn("[dump-error] write lock failed. {}", groupKey); + return false; + } + + try { + final String md5 = MD5.getInstance().getMD5String(content); + if (!PropertyUtil.isStandaloneMode()) { + String loacalMd5 = DiskUtil.getLocalConfigMd5(dataId, group, tenant); + if(md5.equals(loacalMd5)) { + dumpLog.warn("[dump-ignore] ignore to save cache file. groupKey={}, md5={}, lastModifiedOld={}, lastModifiedNew={}", new Object[]{groupKey, md5, ConfigService.getLastModifiedTs(groupKey), lastModifiedTs}); + } else { + DiskUtil.saveToDisk(dataId, group, tenant, content); + } + } + updateMd5(groupKey, md5, lastModifiedTs); + return true; + } catch (IOException ioe) { + dumpLog.error("[dump-exception] save disk error. " + groupKey + ", " + ioe.toString(), + ioe); + return false; + } finally { + releaseWriteLock(groupKey); + } + } + + static public void reloadConfig() + { + String aggreds = null; + try { + if (PropertyUtil.isStandaloneMode()) { + ConfigInfoBase config = persistService.findConfigInfoBase(AggrWhitelist.AGGRIDS_METADATA, "DEFAULT_GROUP"); + if (config != null) { + aggreds = config.getContent(); + } + } else { + aggreds = DiskUtil.getConfig(AggrWhitelist.AGGRIDS_METADATA, + "DEFAULT_GROUP", StringUtils.EMPTY); + } + if (aggreds != null) { + AggrWhitelist.load(aggreds); + } + } catch (IOException e) { + dumpLog.error("reload fail:" + AggrWhitelist.AGGRIDS_METADATA, e); + } + + String clientIpWhitelist = null; + try { + if (PropertyUtil.isStandaloneMode()) { + ConfigInfoBase config = persistService.findConfigInfoBase(ClientIpWhiteList.CLIENT_IP_WHITELIST_METADATA, "DEFAULT_GROUP"); + if (config != null) { + clientIpWhitelist = config.getContent(); + } + } else { + clientIpWhitelist = DiskUtil.getConfig(ClientIpWhiteList.CLIENT_IP_WHITELIST_METADATA, "DEFAULT_GROUP", + StringUtils.EMPTY); + } + if (clientIpWhitelist != null) { + ClientIpWhiteList.load(clientIpWhitelist); + } + } catch (IOException e) { + dumpLog.error("reload fail:" + + ClientIpWhiteList.CLIENT_IP_WHITELIST_METADATA, e); + } + + String switchContent= null; + try { + if (PropertyUtil.isStandaloneMode()) { + ConfigInfoBase config = persistService.findConfigInfoBase(SwitchService.SWITCH_META_DATAID, "DEFAULT_GROUP"); + if (config != null) { + switchContent = config.getContent(); + } + } else { + switchContent = DiskUtil.getConfig( + SwitchService.SWITCH_META_DATAID, "DEFAULT_GROUP", StringUtils.EMPTY); + } + if (switchContent != null) { + SwitchService.load(switchContent); + } + } catch (IOException e) { + dumpLog.error("reload fail:" + SwitchService.SWITCH_META_DATAID, e); + } + + } + + static public List checkMd5() { + List diffList = new ArrayList(); + long startTime = System.currentTimeMillis(); + for (Entry entry : CACHE.entrySet()) { + String groupKey = entry.getKey(); + String[] dg = GroupKey.parseKey(groupKey); + String dataId = dg[0]; + String group = dg[1]; + String tenant = dg[2]; + try { + String loacalMd5 = DiskUtil.getLocalConfigMd5(dataId, group, tenant); + if (!entry.getValue().md5.equals(loacalMd5)) { + defaultLog.warn("[md5-different] dataId:{},group:{}", + dataId, group); + diffList.add(groupKey); + } + } catch (IOException e) { + defaultLog.error("getLocalConfigMd5 fail,dataId:{},group:{}", + dataId, group); + } + } + long endTime = System.currentTimeMillis(); + defaultLog.warn("checkMd5 cost:{}; diffCount:{}", endTime - startTime, + diffList.size()); + return diffList; + } + + /** + * 删除配置文件,删除缓存。 + */ + static public boolean remove(String dataId, String group, String tenant) { + final String groupKey = GroupKey2.getKey(dataId, group, tenant); + final int lockResult = tryWriteLock(groupKey); + /** + * 数据不存在 + */ + if (0 == lockResult) { + dumpLog.info("[remove-ok] {} not exist.", groupKey); + return true; + } + /** + * 加锁失败 + */ + if (lockResult < 0) { + dumpLog.warn("[remove-error] write lock failed. {}", groupKey); + return false; + } + + try { + if (!PropertyUtil.isStandaloneMode()) { + DiskUtil.removeConfigInfo(dataId, group, tenant); + } + CACHE.remove(groupKey); + EventDispatcher.fireEvent(new LocalDataChangeEvent(groupKey)); + + return true; + } finally { + releaseWriteLock(groupKey); + } + } + /** + * 删除配置文件,删除缓存。 + */ + static public boolean removeBeta(String dataId, String group, String tenant) { + final String groupKey = GroupKey2.getKey(dataId, group, tenant); + final int lockResult = tryWriteLock(groupKey); + /** + * 数据不存在 + */ + if (0 == lockResult) { + dumpLog.info("[remove-ok] {} not exist.", groupKey); + return true; + } + /** + * 加锁失败 + */ + if (lockResult < 0) { + dumpLog.warn("[remove-error] write lock failed. {}", groupKey); + return false; + } + + try { + if (!PropertyUtil.isStandaloneMode()) { + DiskUtil.removeConfigInfo4Beta(dataId, group, tenant); + } + EventDispatcher.fireEvent(new LocalDataChangeEvent(groupKey, true, CACHE.get(groupKey).getIps4Beta())); + CACHE.get(groupKey).setBeta(false); + CACHE.get(groupKey).setIps4Beta(null); + CACHE.get(groupKey).setMd54Beta(Constants.NULL); + return true; + } finally { + releaseWriteLock(groupKey); + } + } + + /** + * 删除配置文件,删除缓存。 + */ + static public boolean removeTag(String dataId, String group, String tenant, String tag) { + final String groupKey = GroupKey2.getKey(dataId, group, tenant); + final int lockResult = tryWriteLock(groupKey); + /** + * 数据不存在 + */ + if (0 == lockResult) { + dumpLog.info("[remove-ok] {} not exist.", groupKey); + return true; + } + /** + * 加锁失败 + */ + if (lockResult < 0) { + dumpLog.warn("[remove-error] write lock failed. {}", groupKey); + return false; + } + + try { + if (!PropertyUtil.isStandaloneMode()) { + DiskUtil.removeConfigInfo4Tag(dataId, group, tenant, tag); + } + + CacheItem ci = CACHE.get(groupKey); + ci.tagMd5.remove(tag); + ci.tagLastModifiedTs.remove(tag); + EventDispatcher.fireEvent(new LocalDataChangeEvent(groupKey, false, null, tag)); + return true; + } finally { + releaseWriteLock(groupKey); + } + } + + public static void updateMd5(String groupKey, String md5, long lastModifiedTs) { + CacheItem cache = makeSure(groupKey); + if (cache.md5 ==null || !cache.md5.equals(md5)) { + cache.md5 = md5; + cache.lastModifiedTs = lastModifiedTs; + EventDispatcher.fireEvent(new LocalDataChangeEvent(groupKey)); + } + } + + public static void updateBetaMd5(String groupKey, String md5, List ips4Beta, long lastModifiedTs) { + CacheItem cache = makeSure(groupKey); + if (cache.md54Beta ==null || !cache.md54Beta.equals(md5)) { + cache.isBeta = true; + cache.md54Beta = md5; + cache.lastModifiedTs4Beta = lastModifiedTs; + cache.ips4Beta = ips4Beta; + EventDispatcher.fireEvent(new LocalDataChangeEvent(groupKey, true, ips4Beta)); + } + } + + public static void updateTagMd5(String groupKey, String tag, String md5, long lastModifiedTs) { + CacheItem cache = makeSure(groupKey); + if (cache.tagMd5 == null) { + Map tagMd5Tmp = new HashMap(1); + tagMd5Tmp.put(tag, md5); + cache.tagMd5 = tagMd5Tmp; + if (cache.tagLastModifiedTs == null) { + Map tagLastModifiedTsTmp = new HashMap(1); + tagLastModifiedTsTmp.put(tag, lastModifiedTs); + cache.tagLastModifiedTs = tagLastModifiedTsTmp; + } else { + cache.tagLastModifiedTs.put(tag, lastModifiedTs); + } + EventDispatcher.fireEvent(new LocalDataChangeEvent(groupKey, false, null, tag)); + return; + } + if (cache.tagMd5.get(tag) == null || !cache.tagMd5.get(tag).equals(md5)) { + cache.tagMd5.put(tag, md5); + cache.tagLastModifiedTs.put(tag, lastModifiedTs); + EventDispatcher.fireEvent(new LocalDataChangeEvent(groupKey, false, null, tag)); + } + } + + + /** + * 返回cache的md5。零长度字符串表示没有该数据。 + */ + static public String getContentMd5(String groupKey) { + CacheItem item = CACHE.get(groupKey); + return (null != item) ? item.md5 : Constants.NULL; + } + + /** + * 返回cache的md5。零长度字符串表示没有该数据。 + */ + static public String getContentBetaMd5(String groupKey) { + CacheItem item = CACHE.get(groupKey); + return (null != item) ? item.md54Beta : Constants.NULL; + } + + /** + * 返回cache的md5。零长度字符串表示没有该数据。 + */ + static public String getContentTagMd5(String groupKey, String tag) { + CacheItem item = CACHE.get(groupKey); + if (item == null) { + return Constants.NULL; + } + if (item.tagMd5 == null) { + return Constants.NULL; + } + return item.tagMd5.get(tag); + } + + /** + * 返回beta Ip列表 + */ + static public List getBetaIps(String groupKey) { + CacheItem item = CACHE.get(groupKey); + return (null != item) ? item.getIps4Beta() : Collections.emptyList(); + } + + /** + * 返回cache。 + */ + static public CacheItem getContentCache(String groupKey) { + return CACHE.get(groupKey); + } + + static public String getContentMd5(String groupKey, String ip, String tag) { + CacheItem item = CACHE.get(groupKey); + if (item != null && item.isBeta) { + if (item.ips4Beta.contains(ip)) { + return item.md54Beta; + } + } + if (item != null && item.tagMd5 != null && item.tagMd5.size() > 0) { + if (StringUtils.isNotBlank(tag) && item.tagMd5.containsKey(tag)) { + return item.tagMd5.get(tag); + } + } + return (null != item) ? item.md5 : Constants.NULL; + } + + static public long getLastModifiedTs(String groupKey) { + CacheItem item = CACHE.get(groupKey); + return (null != item) ? item.lastModifiedTs : 0L; + } + + static public boolean isUptodate(String groupKey, String md5) { + String serverMd5 = ConfigService.getContentMd5(groupKey); + return StringUtils.equals(md5, serverMd5); + } + + static public boolean isUptodate(String groupKey, String md5, String ip, String tag) { + String serverMd5 = ConfigService.getContentMd5(groupKey, ip, tag); + return StringUtils.equals(md5, serverMd5); + } + + /** + * 给数据加读锁。如果成功,后面必须调用{@link #releaseReadLock(String)},失败则不需要。 + * + * @param groupKey + * @return 零表示没有数据,失败。正数表示成功,负数表示有写锁导致加锁失败。 + */ + static public int tryReadLock(String groupKey) { + CacheItem groupItem = CACHE.get(groupKey); + int result = (null == groupItem) ? 0 : (groupItem.rwLock.tryReadLock() ? 1 : -1); + if (result < 0) { + defaultLog.warn("[read-lock] failed, {}, {}", result, groupKey); + } + return result; + } + + static public void releaseReadLock(String groupKey) { + CacheItem item = CACHE.get(groupKey); + if (null != item) { + item.rwLock.releaseReadLock(); + } + } + + /** + * 给数据加写锁。如果成功,后面必须调用{@link #releaseWriteLock(String)},失败则不需要。 + * + * @param groupKey + * @return 零表示没有数据,失败。正数表示成功,负数表示加锁失败。 + */ + static int tryWriteLock(String groupKey) { + CacheItem groupItem = CACHE.get(groupKey); + int result = (null == groupItem) ? 0 : (groupItem.rwLock.tryWriteLock() ? 1 : -1); + if (result < 0) { + defaultLog.warn("[write-lock] failed, {}, {}", result, groupKey); + } + return result; + } + + static void releaseWriteLock(String groupKey) { + CacheItem groupItem = CACHE.get(groupKey); + if (null != groupItem) { + groupItem.rwLock.releaseWriteLock(); + } + } + + + static CacheItem makeSure(final String groupKey) { + CacheItem item = CACHE.get(groupKey); + if (null != item) { + return item; + } + CacheItem tmp = new CacheItem(groupKey); + item = CACHE.putIfAbsent(groupKey, tmp); + return (null == item) ? tmp : item; + } + + + private final static String NO_SPACE_CN = "设备上没有空间"; + private final static String NO_SPACE_EN = "No space left on device"; + private final static String DISK_QUATA_CN = "超出磁盘限额"; + private final static String DISK_QUATA_EN = "Disk quota exceeded"; + static final Logger log = LoggerFactory.getLogger(ConfigService.class); + /** + * groupKey -> cacheItem + */ + static private final ConcurrentHashMap CACHE = + new ConcurrentHashMap(); +} + diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigSubService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigSubService.java new file mode 100644 index 00000000000..296542a62ec --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigSubService.java @@ -0,0 +1,265 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service; + +import java.net.HttpURLConnection; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletionService; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorCompletionService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.apache.commons.lang.StringUtils; +import org.codehaus.jackson.type.TypeReference; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.model.SampleResult; +import com.alibaba.nacos.config.server.service.notify.NotifyService; +import com.alibaba.nacos.config.server.utils.JSONUtils; +import com.alibaba.nacos.config.server.utils.LogUtil; +import com.alibaba.nacos.config.server.utils.RunningConfigUtils; +import com.alibaba.nacos.config.server.utils.ThreadUtil; +/** + * config sub service + * @author Nacos + * + */ +@Service +public class ConfigSubService { + + private ScheduledExecutorService scheduler; + + private ServerListService serverListService; + + @Autowired + @SuppressWarnings("PMD.ThreadPoolCreationRule") + public ConfigSubService(ServerListService serverListService1) { + this.serverListService = serverListService1; + + scheduler = Executors.newScheduledThreadPool( + ThreadUtil.getSuitableThreadCount(), new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setDaemon(true); + t.setName("com.alibaba.nacos.ConfigSubService"); + return t; + } + }); + } + + protected ConfigSubService() { + + } + + /** + * 获得调用的URL + * @param ip ip + * @param relativePath path + * @return all path + */ + private String getUrl(String ip, String relativePath) { + return "http://" + ip + RunningConfigUtils.getContextPath() + relativePath; + } + + private List runCollectionJob(String url, Map params, + CompletionService completionService, + List resultList) { + + List ipList = serverListService.getServerList(); + List collectionResult = new ArrayList( + ipList.size()); + // 提交查询任务 + for (String ip : ipList) { + try { + completionService.submit(new Job(ip, url, params)); + } catch (Exception e) { // 发送请求失败 + LogUtil.defaultLog + .warn("Get client info from {} with exception: {} during submit job", + ip, e.getMessage()); + } + } + // 获取结果并合并 + SampleResult sampleResults = null; + for (int i = 0; i < ipList.size(); i++) { + try { + Future f = completionService.poll(1000, + TimeUnit.MILLISECONDS); + try { + if (f != null) { + sampleResults = f.get(500, TimeUnit.MILLISECONDS); + if (sampleResults != null) { + collectionResult.add(sampleResults); + } + } else { + LogUtil.defaultLog + .warn("The task in ip: {} did not completed in 1000ms ", + ipList.get(i)); + } + } catch (TimeoutException e) { + f.cancel(true); + LogUtil.defaultLog.warn( + "get task result with TimeoutException: {} ", e + .getMessage()); + } + } catch (InterruptedException e) { + LogUtil.defaultLog.warn( + "get task result with InterruptedException: {} ", e + .getMessage()); + } catch (ExecutionException e) { + LogUtil.defaultLog.warn( + "get task result with ExecutionException: {} ", e + .getMessage()); + } + } + return collectionResult; + } + + public SampleResult mergeSampleResult(SampleResult sampleCollectResult, List sampleResults) { + SampleResult mergeResult = new SampleResult(); + Map lisentersGroupkeyStatus = null; + if (sampleCollectResult.getLisentersGroupkeyStatus() == null + || sampleCollectResult.getLisentersGroupkeyStatus().isEmpty()) { + lisentersGroupkeyStatus = new HashMap(10); + } else { + lisentersGroupkeyStatus = sampleCollectResult.getLisentersGroupkeyStatus(); + } + + for (SampleResult sampleResult : sampleResults) { + Map lisentersGroupkeyStatusTmp = sampleResult.getLisentersGroupkeyStatus(); + for (Map.Entry entry : lisentersGroupkeyStatusTmp.entrySet()) { + lisentersGroupkeyStatus.put(entry.getKey(), entry.getValue()); + } + } + mergeResult.setLisentersGroupkeyStatus(lisentersGroupkeyStatus); + return mergeResult; + } + + /** + * 去每个Nacos Server节点查询订阅者的任务 + * @author Nacos + */ + class Job implements Callable { + private String ip; + private String url; + private Map params; + + public Job(String ip, String url, Map params) { + this.ip = ip; + this.url = url; + this.params = params; + } + + @Override + public SampleResult call() throws Exception { + + try { + StringBuilder paramUrl = new StringBuilder(); + for (Map.Entry param : params.entrySet()) { + paramUrl.append("&").append(param.getKey()).append("=") + .append(URLEncoder.encode(param.getValue(), Constants.ENCODE)); + } + + String urlAll = getUrl(ip, url) + paramUrl; + com.alibaba.nacos.config.server.service.notify.NotifyService.HttpResult result = NotifyService + .invokeURL(urlAll, null, Constants.ENCODE); + /** + * http code 200 + */ + if (result.code == HttpURLConnection.HTTP_OK) { + String json = result.content; + Object resultObj = JSONUtils.deserializeObject(json, + new TypeReference() { + }); + return (SampleResult) resultObj; + + } else { + + LogUtil.defaultLog.info( + "Can not get clientInfo from {} with {}", ip, + result.code); + return null; + } + } catch (Exception e) { + LogUtil.defaultLog.warn( + "Get client info from {} with exception: {}", ip, e + .getMessage()); + return null; + } + } + } + + public SampleResult getCollectSampleResult(String dataId, String group, String tenant, int sampleTime) + throws Exception { + List resultList = new ArrayList(); + String url = Constants.COMMUNICATION_CONTROLLER_PATH + "/configWatchers"; + Map params =new HashMap(5); + params.put("dataId", dataId); + params.put("group", group); + if (!StringUtils.isBlank(tenant)) { + params.put("tenant", tenant); + } + BlockingQueue> queue = new LinkedBlockingDeque>( + serverListService.getServerList().size()); + CompletionService completionService = new ExecutorCompletionService(scheduler, + queue); + + SampleResult sampleCollectResult = new SampleResult(); + for (int i = 0; i < sampleTime; i++) { + List sampleResults = runCollectionJob(url, params, completionService, resultList); + if (sampleResults != null) { + sampleCollectResult = mergeSampleResult(sampleCollectResult, sampleResults); + } + } + return sampleCollectResult; + } + + public SampleResult getCollectSampleResultByIp(String ip, int sampleTime) + throws Exception { + List resultList = new ArrayList(10); + String url = Constants.COMMUNICATION_CONTROLLER_PATH + "/watcherConfigs"; + Map params =new HashMap(50); + params.put("ip", ip); + BlockingQueue> queue = new LinkedBlockingDeque>( + serverListService.getServerList().size()); + CompletionService completionService = new ExecutorCompletionService(scheduler, + queue); + + SampleResult sampleCollectResult = new SampleResult(); + for (int i = 0; i < sampleTime; i++) { + List sampleResults = runCollectionJob(url, params, completionService, resultList); + if (sampleResults != null) { + sampleCollectResult = mergeSampleResult(sampleCollectResult, sampleResults); + } + } + return sampleCollectResult; + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/DataSourceService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/DataSourceService.java new file mode 100644 index 00000000000..af4cd63dbad --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/DataSourceService.java @@ -0,0 +1,72 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.transaction.support.TransactionTemplate; + +import java.io.IOException; + +/** + * datasource interface + * + * @author Nacos + * + */ +public interface DataSourceService { + /** + * reload + * + * @throws IOException + * exception + */ + void reload() throws IOException; + + /** + * check master db + * + * @return is master + */ + boolean checkMasterWritable(); + + /** + * get jdbc template + * + * @return JdbcTemplate + */ + JdbcTemplate getJdbcTemplate(); + + /** + * get transaction template + * + * @return TransactionTemplate + */ + TransactionTemplate getTransactionTemplate(); + + /** + * get current db url + * + * @return + */ + String getCurrentDBUrl(); + + /** + * get heath + * + * @return heath info + */ + String getHealth(); +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/DiskUtil.java b/config/src/main/java/com/alibaba/nacos/config/server/service/DiskUtil.java new file mode 100644 index 00000000000..24c1797af4d --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/DiskUtil.java @@ -0,0 +1,260 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.utils.LogUtil; +import com.alibaba.nacos.config.server.utils.MD5; + + +/** + * 磁盘操作工具类。 + * + * 只有一个dump线程。 + * + * @author jiuRen + */ +public class DiskUtil { + + static final Logger logger = LoggerFactory.getLogger(DiskUtil.class); + static String APP_HOME = System.getProperty("user.home") + File.separator + "nacos"; + static final String BASE_DIR = File.separator + "data" + File.separator + "config-data"; + static final String TENANT_BASE_DIR = File.separator + "data" + File.separator + "tenant-config-data"; + static final String BETA_DIR = File.separator + "data" + File.separator + "beta-data"; + static final String TENANT_BETA_DIR = File.separator + "data " + File.separator + "tenant-beta-data"; + static final String TAG_DIR = File.separator + "data" + File.separator + "tag-data"; + static final String TENANT_TAG_DIR = File.separator + "data" + File.separator + "tag-beta-data"; + static String SERVERLIST_DIR = File.separator + "conf"; + static String SERVERLIST_FILENAME = "cluster.conf"; + + static { + String nacosDir = System.getProperty("nacos.home"); + if (!StringUtils.isBlank(nacosDir)) { + APP_HOME = nacosDir; + } + } + + + static public void saveHeartBeatToDisk(String heartBeatTime) + throws IOException { + FileUtils.writeStringToFile(heartBeatFile(), heartBeatTime, + Constants.ENCODE); + } + + /** + * 保存配置信息到磁盘 + */ + static public void saveToDisk(String dataId, String group, String tenant, String content) throws IOException { + File targetFile = targetFile(dataId, group, tenant); + FileUtils.writeStringToFile(targetFile, content, Constants.ENCODE); + } + + /** + * 保存配置信息到磁盘 + */ + static public void saveBetaToDisk(String dataId, String group, String tenant, String content) throws IOException { + File targetFile = targetBetaFile(dataId, group, tenant); + FileUtils.writeStringToFile(targetFile, content, Constants.ENCODE); + } + + /** + * 保存配置信息到磁盘 + */ + static public void saveTagToDisk(String dataId, String group, String tenant, String tag, String content) + throws IOException { + File targetFile = targetTagFile(dataId, group, tenant, tag); + FileUtils.writeStringToFile(targetFile, content, Constants.ENCODE); + } + + /** + * 删除磁盘上的配置文件 + */ + static public void removeConfigInfo(String dataId, String group, String tenant) { + FileUtils.deleteQuietly(targetFile(dataId, group, tenant)); + } + + /** + * 删除磁盘上的配置文件 + */ + static public void removeConfigInfo4Beta(String dataId, String group, String tenant) { + + FileUtils.deleteQuietly(targetBetaFile(dataId, group, tenant)); + } + + /** + * 删除磁盘上的配置文件 + */ + static public void removeConfigInfo4Tag(String dataId, String group, String tenant, String tag) { + + FileUtils.deleteQuietly(targetTagFile(dataId, group, tenant, tag)); + } + + static public void removeHeartHeat() { + FileUtils.deleteQuietly(heartBeatFile()); + } + + /** + * 返回服务端缓存文件的路径 + */ + static public File targetFile(String dataId, String group, String tenant) { + File file = null; + if (StringUtils.isBlank(tenant)) { + file = new File(APP_HOME, BASE_DIR); + } else { + file = new File(APP_HOME, TENANT_BASE_DIR); + file = new File(file, tenant); + } + file = new File(file, group); + file = new File(file, dataId); + return file; + } + + /** + * 返回服务端beta缓存文件的路径 + */ + static public File targetBetaFile(String dataId, String group, String tenant) { + File file = null; + if (StringUtils.isBlank(tenant)) { + file = new File(APP_HOME, BETA_DIR); + } else { + file = new File(APP_HOME, TENANT_BETA_DIR); + file = new File(file, tenant); + } + file = new File(file, group); + file = new File(file, dataId); + return file; + } + + /** + * 返回服务端Tag缓存文件的路径 + */ + static public File targetTagFile(String dataId, String group, String tenant, String tag) { + File file = null; + if (StringUtils.isBlank(tenant)) { + file = new File(APP_HOME, TAG_DIR); + } else { + file = new File(APP_HOME, TENANT_TAG_DIR); + file = new File(file, tenant); + } + file = new File(file, group); + file = new File(file, dataId); + file = new File(file, tag); + return file; + } + + static public String getConfig(String dataId, String group, String tenant) + throws IOException { + FileInputStream fis = null; + File file = targetFile(dataId, group, tenant); + if (file.exists()) { + try { + fis = new FileInputStream(file); + } catch (FileNotFoundException e) { + return StringUtils.EMPTY; + } + String content = IOUtils.toString(fis, Constants.ENCODE); + return content; + } else { + return StringUtils.EMPTY; + } + } + + static public String getLocalConfigMd5(String dataId, String group, String tenant) + throws IOException { + return MD5.getInstance().getMD5String(getConfig(dataId, group, tenant)); + } + + static public File heartBeatFile() { + return new File(APP_HOME, "status/heartBeat.txt"); + } + + static public String relativePath(String dataId, String group) { + return BASE_DIR + "/" + dataId + "/" + group; + } + + static public void clearAll() { + File file = new File(APP_HOME, BASE_DIR); + if (FileUtils.deleteQuietly(file)) { + LogUtil.defaultLog.info("clear all config-info success."); + } else { + LogUtil.defaultLog.warn("clear all config-info failed."); + } + File fileTenant = new File(APP_HOME, TENANT_BASE_DIR); + if (FileUtils.deleteQuietly(fileTenant)) { + LogUtil.defaultLog.info("clear all config-info-tenant success."); + } else { + LogUtil.defaultLog.warn("clear all config-info-tenant failed."); + } + } + + static public void clearAllBeta() { + File file = new File(APP_HOME, BETA_DIR); + if (FileUtils.deleteQuietly(file)) { + LogUtil.defaultLog.info("clear all config-info-beta success."); + } else { + LogUtil.defaultLog.warn("clear all config-info-beta failed."); + } + File fileTenant = new File(APP_HOME, TENANT_BETA_DIR); + if (FileUtils.deleteQuietly(fileTenant)) { + LogUtil.defaultLog.info("clear all config-info-beta-tenant success."); + } else { + LogUtil.defaultLog.warn("clear all config-info-beta-tenant failed."); + } + } + + static public void clearAllTag() { + File file = new File(APP_HOME, TAG_DIR); + if (FileUtils.deleteQuietly(file)) { + LogUtil.defaultLog.info("clear all config-info-tag success."); + } else { + LogUtil.defaultLog.warn("clear all config-info-tag failed."); + } + File fileTenant = new File(APP_HOME, TENANT_TAG_DIR); + if (FileUtils.deleteQuietly(fileTenant)) { + LogUtil.defaultLog.info("clear all config-info-tag-tenant success."); + } else { + LogUtil.defaultLog.warn("clear all config-info-tag-tenant failed."); + } + } + + public static String getServerList() throws IOException { + FileInputStream fis = null; + File file = new File(APP_HOME, SERVERLIST_DIR); + file = new File(file, SERVERLIST_FILENAME); + if (file.exists()) { + try { + fis = new FileInputStream(file); + } catch (FileNotFoundException e) { + return StringUtils.EMPTY; + } + String content = IOUtils.toString(fis, Constants.ENCODE); + return content; + } else { + return StringUtils.EMPTY; + } + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/DynamicDataSource.java b/config/src/main/java/com/alibaba/nacos/config/server/service/DynamicDataSource.java new file mode 100644 index 00000000000..b720aaf3e4c --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/DynamicDataSource.java @@ -0,0 +1,52 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service; + +import com.alibaba.nacos.config.server.utils.PropertyUtil; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + * datasource adapter + * @author Nacos + * + */ +@Component +public class DynamicDataSource implements ApplicationContextAware { + private ApplicationContext applicationContext; + + public void setApplicationContext(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + public ApplicationContext getApplicationContext() { + return applicationContext; + } + + public DataSourceService getDataSource() { + DataSourceService dataSourceService = null; + + if (PropertyUtil.isStandaloneMode()) { + dataSourceService = (DataSourceService)applicationContext.getBean("localDataSourceService"); + } else { + dataSourceService = (DataSourceService)applicationContext.getBean("basicDataSourceService"); + } + + return dataSourceService; + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/LocalDataChangeEvent.java b/config/src/main/java/com/alibaba/nacos/config/server/service/LocalDataChangeEvent.java new file mode 100644 index 00000000000..c88a88982a6 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/LocalDataChangeEvent.java @@ -0,0 +1,53 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service; + +import java.util.List; + +import com.alibaba.nacos.config.server.utils.event.EventDispatcher.Event; + +/** + * 本地数据发生变更的事件。 + * @author Nacos + */ +public class LocalDataChangeEvent implements Event { + final public String groupKey; + final public boolean isBeta; + final public List betaIps; + final public String tag; + + + public LocalDataChangeEvent(String groupKey) { + this.groupKey = groupKey; + this.isBeta = false; + this.betaIps = null; + this.tag = null; + } + + public LocalDataChangeEvent(String groupKey, boolean isBeta, List betaIps) { + this.groupKey = groupKey; + this.isBeta = isBeta; + this.betaIps = betaIps; + this.tag = null; + } + + public LocalDataChangeEvent(String groupKey, boolean isBeta, List betaIps, String tag) { + this.groupKey = groupKey; + this.isBeta = isBeta; + this.betaIps = betaIps; + this.tag = tag; + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/LocalDataSourceServiceImpl.java b/config/src/main/java/com/alibaba/nacos/config/server/service/LocalDataSourceServiceImpl.java new file mode 100644 index 00000000000..db08bc1b77f --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/LocalDataSourceServiceImpl.java @@ -0,0 +1,205 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.net.URL; +import java.sql.Connection; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import javax.annotation.PostConstruct; +import javax.sql.DataSource; + +import com.alibaba.nacos.config.server.utils.PropertyUtil; +import org.apache.commons.dbcp.BasicDataSource; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.stereotype.Service; +import org.springframework.transaction.support.TransactionTemplate; + +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.utils.LogUtil; +import com.alibaba.nacos.config.server.utils.StringUtils; + +/** + * local data source + * + * @author Nacos + * + */ +@Service("localDataSourceService") +public class LocalDataSourceServiceImpl implements DataSourceService { + private static final String JDBC_DRIVER_NAME = "org.apache.derby.jdbc.EmbeddedDriver"; + private static final String DERBY_BASE_DIR = "data" + File.separator + "derby-data"; + private static String appHome = System.getProperty("user.home") + File.separator + "nacos"; + private static final String NACOS_HOME_KEY = "nacos.home"; + private static final String USER_NAME = "nacos"; + private static final String PASSWORD = "nacos"; + + private JdbcTemplate jt; + private TransactionTemplate tjt; + + @PostConstruct + public void init() { + String nacosBaseDir = System.getProperty(NACOS_HOME_KEY); + if (!StringUtils.isBlank(nacosBaseDir)) { + setAppHome(nacosBaseDir); + } + BasicDataSource ds = new BasicDataSource(); + ds.setDriverClassName(JDBC_DRIVER_NAME); + ds.setUrl("jdbc:derby:" + appHome + File.separator + DERBY_BASE_DIR + ";create=true"); + ds.setUsername(USER_NAME); + ds.setPassword(PASSWORD); + ds.setInitialSize(20); + ds.setMaxActive(30); + ds.setMaxIdle(50); + ds.setMaxWait(10000L); + ds.setPoolPreparedStatements(true); + ds.setTimeBetweenEvictionRunsMillis(TimeUnit.MINUTES + .toMillis(10L)); + ds.setTestWhileIdle(true); + + jt = new JdbcTemplate(); + jt.setMaxRows(50000); + jt.setQueryTimeout(5000); + jt.setDataSource(ds); + DataSourceTransactionManager tm = new DataSourceTransactionManager(); + tjt = new TransactionTemplate(tm); + tm.setDataSource(ds); + tjt.setTimeout(5000); + + if (PropertyUtil.isStandaloneMode()) { + reload(); + } + } + + @Override + public void reload() { + DataSource ds = jt.getDataSource(); + if (ds == null) { + throw new RuntimeException("datasource is null"); + } + try { + execute(ds.getConnection(), "schema.sql"); + } catch (Exception e) { + throw new RuntimeException("load schema.sql error." + e); + } + } + + @Override + public boolean checkMasterWritable() { + return true; + } + + @Override + public JdbcTemplate getJdbcTemplate() { + return jt; + } + + @Override + public TransactionTemplate getTransactionTemplate() { + return tjt; + } + + @Override + public String getCurrentDBUrl() { + return "jdbc:derby:" + appHome + File.separator + DERBY_BASE_DIR + ";create=true"; + } + + @Override + public String getHealth() { + return "UP"; + } + + /** + * 读取SQL文件 + * @param sqlFile sql + * @return sqls + * @throws Exception Exception + */ + private List loadSql(String sqlFile) throws Exception { + List sqlList = new ArrayList(); + InputStream sqlFileIn = null; + try { + if (StringUtils.isBlank(System.getProperty(NACOS_HOME_KEY))) { + ClassLoader classLoader = getClass().getClassLoader(); + URL url = classLoader.getResource(sqlFile); + sqlFileIn = new FileInputStream(url.getFile()); + } else { + File file = new File(System.getProperty(NACOS_HOME_KEY) + File.separator + "conf" + File.separator + sqlFile); + sqlFileIn = new FileInputStream(file); + } + + StringBuffer sqlSb = new StringBuffer(); + byte[] buff = new byte[1024]; + int byteRead = 0; + while ((byteRead = sqlFileIn.read(buff)) != -1) { + sqlSb.append(new String(buff, 0, byteRead, Constants.ENCODE)); + } + + String[] sqlArr = sqlSb.toString().split(";"); + for (int i = 0; i < sqlArr.length; i++) { + String sql = sqlArr[i].replaceAll("--.*", "").trim(); + if (StringUtils.isNotEmpty(sql)) { + sqlList.add(sql); + } + } + return sqlList; + } catch (Exception ex) { + throw new Exception(ex.getMessage()); + } finally { + if (sqlFileIn != null) { + sqlFileIn.close(); + } + } + } + + /** + * 执行SQL语句 + * @param conn connect + * @param sqlFile sql + * @throws Exception Exception + */ + public void execute(Connection conn, String sqlFile) throws Exception { + Statement stmt = null; + List sqlList = loadSql(sqlFile); + stmt = conn.createStatement(); + for (String sql : sqlList) { + try { + stmt.execute(sql); + } catch (Exception e) { + LogUtil.defaultLog.info(e.getMessage()); + } + + } + stmt.close(); + } + + public static String getAppHome() { + return appHome; + } + + public static void setAppHome(String appHome) { + LocalDataSourceServiceImpl.appHome = appHome; + } + + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/LongPullingService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/LongPullingService.java new file mode 100755 index 00000000000..beed7b2368e --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/LongPullingService.java @@ -0,0 +1,513 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service; + +import static com.alibaba.nacos.config.server.utils.LogUtil.memoryLog; +import static com.alibaba.nacos.config.server.utils.LogUtil.pullLog; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +import javax.servlet.AsyncContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang.StringUtils; +import org.springframework.stereotype.Service; + +import com.alibaba.nacos.config.server.model.SampleResult; +import com.alibaba.nacos.config.server.utils.GroupKey; +import com.alibaba.nacos.config.server.utils.LogUtil; +import com.alibaba.nacos.config.server.utils.MD5Util; +import com.alibaba.nacos.config.server.utils.RequestUtil; +import com.alibaba.nacos.config.server.utils.event.EventDispatcher.AbstractEventListener; +import com.alibaba.nacos.config.server.utils.event.EventDispatcher.Event; + +/** + * 长轮询服务。负责处理 + * @author Nacos + */ +@Service +public class LongPullingService extends AbstractEventListener { + + private static final int FIXED_POLLING_INTERVAL_MS = 10000; + + private static final int SAMPLE_PERIOD = 100; + + private static final int SAMPLE_TIMES = 3; + + private static final String TRUE_STR = "true"; + + private Map retainIps = new ConcurrentHashMap(); + + private static boolean isFixedPolling() { + return SwitchService.getSwitchBoolean(SwitchService.FIXED_POLLING, false); + } + + private static int getFixedPollingInterval() { + return SwitchService.getSwitchInteger(SwitchService.FIXED_POLLING_INTERVAL, FIXED_POLLING_INTERVAL_MS); + } + + + public boolean isClientLongPolling(String clientIp) { + return getClientPollingRecord(clientIp) != null; + } + + public Map getClientSubConfigInfo(String clientIp) { + ClientLongPulling record = getClientPollingRecord(clientIp); + + if (record == null) { + return Collections.emptyMap(); + } + + return record.clientMd5Map; + } + + public SampleResult getSubscribleInfo(String dataId, String group, String tenant) { + String groupKey = GroupKey.getKeyTenant(dataId, group, tenant); + SampleResult sampleResult = new SampleResult(); + Map lisentersGroupkeyStatus = new HashMap(50); + + for (ClientLongPulling clientLongPulling : allSubs) { + if (clientLongPulling.clientMd5Map.containsKey(groupKey)) { + lisentersGroupkeyStatus.put(clientLongPulling.ip, clientLongPulling.clientMd5Map.get(groupKey)); + } + } + sampleResult.setLisentersGroupkeyStatus(lisentersGroupkeyStatus); + return sampleResult; + } + + public SampleResult getSubscribleInfoByIp(String clientIp) { + SampleResult sampleResult = new SampleResult(); + Map lisentersGroupkeyStatus = new HashMap(50); + + for (ClientLongPulling clientLongPulling : allSubs) { + if (clientLongPulling.ip.equals(clientIp)) { + // 一个ip可能有多个监听 + if (!lisentersGroupkeyStatus.equals(clientLongPulling.clientMd5Map)) { + lisentersGroupkeyStatus.putAll(clientLongPulling.clientMd5Map); + } + } + } + sampleResult.setLisentersGroupkeyStatus(lisentersGroupkeyStatus); + return sampleResult; + } + + /** + * 聚合采样结果中的采样ip和监听配置的信息;合并策略用后面的覆盖前面的是没有问题的 + * @param sampleResults sample Results + * @return Results + */ + public SampleResult mergeSampleResult(List sampleResults) { + SampleResult mergeResult = new SampleResult(); + Map lisentersGroupkeyStatus = new HashMap(50); + for (SampleResult sampleResult : sampleResults) { + Map lisentersGroupkeyStatusTmp = sampleResult.getLisentersGroupkeyStatus(); + for (Map.Entry entry : lisentersGroupkeyStatusTmp.entrySet()) { + lisentersGroupkeyStatus.put(entry.getKey(), entry.getValue()); + } + } + mergeResult.setLisentersGroupkeyStatus(lisentersGroupkeyStatus); + return mergeResult; + } + + public Map> collectApplicationSubscribeConfigInfos() { + if (allSubs == null || allSubs.isEmpty()) { + return null; + } + HashMap> app2Groupkeys = new HashMap>(50); + for (ClientLongPulling clientLongPulling : allSubs) { + if(StringUtils.isEmpty(clientLongPulling.appName) || "unknown".equalsIgnoreCase(clientLongPulling.appName)) { + continue; + } + Set appSubscribeConfigs = app2Groupkeys.get(clientLongPulling.appName); + Set clientSubscribeConfigs = clientLongPulling.clientMd5Map.keySet(); + if(appSubscribeConfigs==null) { + appSubscribeConfigs = new HashSet(clientSubscribeConfigs.size()); + } + appSubscribeConfigs.addAll(clientSubscribeConfigs); + app2Groupkeys.put(clientLongPulling.appName, appSubscribeConfigs); + } + + return app2Groupkeys; + } + + + public SampleResult getCollectSubscribleInfo(String dataId, String group, String tenant) { + List sampleResultLst = new ArrayList(50); + for (int i = 0; i < SAMPLE_TIMES; i++) { + SampleResult sampleTmp = getSubscribleInfo(dataId, group, tenant); + if (sampleTmp != null) { + sampleResultLst.add(sampleTmp); + } + if (i < SAMPLE_TIMES - 1) { + try { + Thread.sleep(SAMPLE_PERIOD); + } catch (InterruptedException e) { + LogUtil.clientLog.error("sleep wrong", e); + } + } + } + + SampleResult sampleResult = mergeSampleResult(sampleResultLst); + return sampleResult; + } + + public SampleResult getCollectSubscribleInfoByIp(String ip) { + SampleResult sampleResult = new SampleResult(); + sampleResult.setLisentersGroupkeyStatus(new HashMap(50)); + for (int i = 0; i < SAMPLE_TIMES; i++) { + SampleResult sampleTmp = getSubscribleInfoByIp(ip); + if (sampleTmp != null) { + if (sampleTmp.getLisentersGroupkeyStatus() != null + && !sampleResult.getLisentersGroupkeyStatus().equals(sampleTmp.getLisentersGroupkeyStatus())) { + sampleResult.getLisentersGroupkeyStatus().putAll(sampleTmp.getLisentersGroupkeyStatus()); + } + } + if (i < SAMPLE_TIMES - 1) { + try { + Thread.sleep(SAMPLE_PERIOD); + } catch (InterruptedException e) { + LogUtil.clientLog.error("sleep wrong", e); + } + } + } + return sampleResult; + } + + private ClientLongPulling getClientPollingRecord(String clientIp) { + if (allSubs == null) { + return null; + } + + for (ClientLongPulling clientLongPulling : allSubs) { + HttpServletRequest request = (HttpServletRequest) clientLongPulling.asyncContext.getRequest(); + + if (clientIp.equals(RequestUtil.getRemoteIp(request))) { + return clientLongPulling; + } + } + + return null; + } + + public void addLongPullingClient(HttpServletRequest req, HttpServletResponse rsp, Map clientMd5Map, int probeRequestSize) { + + String str = req.getHeader(LongPullingService.LONG_PULLING_HEADER); + String noHangUpFlag = req.getHeader(LongPullingService.LONG_PULLING_NO_HANG_UP_HEADER); + String appName = req.getHeader(RequestUtil.CLIENT_APPNAME_HEADER); + String tag = req.getHeader("Vipserver-Tag"); + int delayTime=SwitchService.getSwitchInteger(SwitchService.FIXED_DELAY_TIME, 500); + /** + * 提前500ms返回响应,为避免客户端超时 @qiaoyi.dingqy 2013.10.22改动 add delay time for LoadBalance + */ + long timeout = Math.max(10000, Long.parseLong(str) - delayTime); + if (isFixedPolling()) { + timeout = Math.max(10000, getFixedPollingInterval()); + // do nothing but set fix polling timeout + } else { + long start = System.currentTimeMillis(); + List changedGroups = MD5Util.compareMd5(req, rsp, clientMd5Map); + if (changedGroups.size() > 0) { + generateResponse(req, rsp, changedGroups); + LogUtil.clientLog.info("{}|{}|{}|{}|{}|{}|{}", + new Object[]{System.currentTimeMillis() - start, "instant", RequestUtil.getRemoteIp(req), "polling", + clientMd5Map.size(), probeRequestSize, changedGroups.size()}); + return; + } else if(noHangUpFlag!=null && noHangUpFlag.equalsIgnoreCase(TRUE_STR)) { + LogUtil.clientLog.info("{}|{}|{}|{}|{}|{}|{}", + new Object[]{System.currentTimeMillis() - start, "nohangup", RequestUtil.getRemoteIp(req), "polling", + clientMd5Map.size(), probeRequestSize, changedGroups.size()}); + return; + } + } + String ip = RequestUtil.getRemoteIp(req); + // 一定要由HTTP线程调用,否则离开后容器会立即发送响应 + final AsyncContext asyncContext = req.startAsync(); + // AsyncContext.setTimeout()的超时时间不准,所以只能自己控制 + asyncContext.setTimeout(0L); + + + scheduler.execute( + new ClientLongPulling(asyncContext, clientMd5Map, ip, probeRequestSize, timeout, appName, tag)); + } + + @Override + public List> interest() { + List> eventTypes = new ArrayList>(); + eventTypes.add(LocalDataChangeEvent.class); + return eventTypes; + } + + @Override + public void onEvent(Event event) { + if (isFixedPolling()) { + // ignore + } else { + if (event instanceof LocalDataChangeEvent) { + LocalDataChangeEvent evt = (LocalDataChangeEvent) event; + scheduler.execute(new DataChangeTask(evt.groupKey, evt.isBeta, evt.betaIps)); + } + } + } + + static public boolean isSupportLongPulling(HttpServletRequest req) { + return null != req.getHeader(LONG_PULLING_HEADER); + } + + @SuppressWarnings("PMD.ThreadPoolCreationRule") + public LongPullingService() { + allSubs = new ConcurrentLinkedQueue(); + + scheduler = Executors.newScheduledThreadPool(1, new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setDaemon(true); + t.setName("com.alibaba.nacos.LongPulling"); + return t; + } + }); + scheduler.scheduleWithFixedDelay(new StatTask(), 0L, 10L, TimeUnit.SECONDS); + } + + // ================= + + static public final String LONG_PULLING_HEADER = "Long-Pulling-Timeout"; + static public final String LONG_PULLING_NO_HANG_UP_HEADER = "Long-Pulling-Timeout-No-Hangup"; + + final ScheduledExecutorService scheduler; + + /** + * 长轮询订阅关系 + */ + final Queue allSubs; + + // ================= + + class DataChangeTask implements Runnable { + @Override + public void run() { + try { + ConfigService.getContentBetaMd5(groupKey); + for (Iterator iter = allSubs.iterator(); iter.hasNext(); ) { + ClientLongPulling clientSub = iter.next(); + if (clientSub.clientMd5Map.containsKey(groupKey)) { + // 如果beta发布且不在beta列表直接跳过 + if (isBeta && !betaIps.contains(clientSub.ip)) { + continue; + } + + // 如果tag发布且不在tag列表直接跳过 + if (StringUtils.isNotBlank(tag) && !tag.equals(clientSub.tag)) { + continue; + } + + getRetainIps().put(clientSub.ip, System.currentTimeMillis()); + iter.remove(); // 删除订阅关系 + LogUtil.clientLog.info("{}|{}|{}|{}|{}|{}|{}", + new Object[]{(System.currentTimeMillis() - changeTime), + "in-advance", RequestUtil.getRemoteIp((HttpServletRequest) clientSub.asyncContext.getRequest()), "polling", + clientSub.clientMd5Map.size(), clientSub.probeRequestSize, groupKey}); + clientSub.sendResponse(Arrays.asList(groupKey)); + } + } + } catch (Throwable t) { + LogUtil.defaultLog.error("data change error:" + t.getMessage(), t.getCause()); + } + } + + DataChangeTask(String groupKey) { + this(groupKey, false, null); + } + + DataChangeTask(String groupKey, boolean isBeta, List betaIps) { + this(groupKey, isBeta, betaIps, null); + } + + DataChangeTask(String groupKey, boolean isBeta, List betaIps, String tag) { + this.groupKey = groupKey; + this.isBeta = isBeta; + this.betaIps = betaIps; + this.tag = tag; + } + + final String groupKey; + final long changeTime = System.currentTimeMillis(); + final boolean isBeta; + final List betaIps; + final String tag; + } + + // ================= + + class StatTask implements Runnable { + @Override + public void run() { + memoryLog.info("[long-pulling] client count " + allSubs.size()); + } + } + + // ================= + + class ClientLongPulling implements Runnable { + + @Override + public void run() { + asyncTimeoutFuture = scheduler.schedule(new Runnable() { + public void run() { + try { + getRetainIps().put(ClientLongPulling.this.ip, System.currentTimeMillis()); + /** + * 删除订阅关系 + */ + allSubs.remove(ClientLongPulling.this); + + if(isFixedPolling()) { + LogUtil.clientLog.info("{}|{}|{}|{}|{}|{}", + new Object[]{(System.currentTimeMillis() - createTime), + "fix", RequestUtil.getRemoteIp((HttpServletRequest) asyncContext.getRequest()), "polling", + clientMd5Map.size(), probeRequestSize}); + List changedGroups = MD5Util.compareMd5((HttpServletRequest) asyncContext.getRequest(), (HttpServletResponse) asyncContext.getResponse(), clientMd5Map); + if (changedGroups.size() > 0) { + sendResponse(changedGroups); + } else { + sendResponse(null); + } + } else { + LogUtil.clientLog.info("{}|{}|{}|{}|{}|{}", + new Object[]{(System.currentTimeMillis() - createTime), + "timeout", RequestUtil.getRemoteIp((HttpServletRequest) asyncContext.getRequest()), "polling", + clientMd5Map.size(), probeRequestSize}); + sendResponse(null); + } + } catch (Throwable t) { + LogUtil.defaultLog.error("long pulling error:" + t.getMessage(), t.getCause()); + } + + } + }, timeoutTime, TimeUnit.MILLISECONDS); + + allSubs.add(this); + } + + void sendResponse(List changedGroups) { + /** + * 取消超时任务 + */ + if (null != asyncTimeoutFuture) { + asyncTimeoutFuture.cancel(false); + } + generateResponse(changedGroups); + } + + void generateResponse(List changedGroups) { + if (null == changedGroups) { + /** + * 告诉容器发送HTTP响应 + */ + asyncContext.complete(); + return; + } + + HttpServletResponse response = (HttpServletResponse) asyncContext.getResponse(); + + try { + String respString = MD5Util.compareMd5ResultString(changedGroups); + + // 禁用缓存 + response.setHeader("Pragma", "no-cache"); + response.setDateHeader("Expires", 0); + response.setHeader("Cache-Control", "no-cache,no-store"); + response.setStatus(HttpServletResponse.SC_OK); + response.getWriter().println(respString); + asyncContext.complete(); + } catch (Exception se) { + pullLog.error(se.toString(), se); + asyncContext.complete(); + } + } + + ClientLongPulling(AsyncContext ac, Map clientMd5Map, String ip, int probeRequestSize, + long timeoutTime, String appName, String tag) { + this.asyncContext = ac; + this.clientMd5Map = clientMd5Map; + this.probeRequestSize = probeRequestSize; + this.createTime = System.currentTimeMillis(); + this.ip = ip; + this.timeoutTime = timeoutTime; + this.appName = appName; + this.tag = tag; + } + + // ================= + + final AsyncContext asyncContext; + final Map clientMd5Map; + final long createTime; + final String ip; + final String appName; + final String tag; + final int probeRequestSize; + final long timeoutTime; + + Future asyncTimeoutFuture; + } + + void generateResponse(HttpServletRequest request, HttpServletResponse response, List changedGroups) { + if (null == changedGroups) { + return; + } + + try { + String respString = MD5Util.compareMd5ResultString(changedGroups); + // 禁用缓存 + response.setHeader("Pragma", "no-cache"); + response.setDateHeader("Expires", 0); + response.setHeader("Cache-Control", "no-cache,no-store"); + response.setStatus(HttpServletResponse.SC_OK); + response.getWriter().println(respString); + } catch (Exception se) { + pullLog.error(se.toString(), se); + } + } + + public Map getRetainIps() { + return retainIps; + } + + public void setRetainIps(Map retainIps) { + this.retainIps = retainIps; + } + + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/PersistService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/PersistService.java new file mode 100755 index 00000000000..6096a1d350b --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/PersistService.java @@ -0,0 +1,3090 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service; + +import static com.alibaba.nacos.config.server.utils.LogUtil.defaultLog; +import static com.alibaba.nacos.config.server.utils.LogUtil.fatalLog; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import javax.annotation.PostConstruct; + +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.jdbc.CannotGetJdbcConnectionException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.PreparedStatementCreator; +import org.springframework.jdbc.core.PreparedStatementSetter; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.TransactionException; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.TransactionSystemException; +import org.springframework.transaction.support.TransactionCallback; +import org.springframework.transaction.support.TransactionTemplate; +import org.springframework.util.CollectionUtils; + +import com.alibaba.nacos.config.server.model.ConfigAdvanceInfo; +import com.alibaba.nacos.config.server.model.ConfigHistoryInfo; +import com.alibaba.nacos.config.server.model.ConfigInfo; +import com.alibaba.nacos.config.server.model.ConfigInfo4Beta; +import com.alibaba.nacos.config.server.model.ConfigInfo4Tag; +import com.alibaba.nacos.config.server.model.ConfigInfoAggr; +import com.alibaba.nacos.config.server.model.ConfigInfoBase; +import com.alibaba.nacos.config.server.model.ConfigInfoChanged; +import com.alibaba.nacos.config.server.model.ConfigKey; +import com.alibaba.nacos.config.server.model.Page; +import com.alibaba.nacos.config.server.model.SubInfo; +import com.alibaba.nacos.config.server.utils.LogUtil; +import com.alibaba.nacos.config.server.utils.MD5; +import com.alibaba.nacos.config.server.utils.PaginationHelper; +import com.alibaba.nacos.config.server.utils.event.EventDispatcher; +import com.google.common.collect.Lists; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +/** + * 数据库服务,提供ConfigInfo在数据库的存取
+ * 3.0开始增加数据版本号, 并将物理删除改为逻辑删除
+ * 3.0增加数据库切换功能 + * + * @author boyan + * @author leiwen.zh + * @since 1.0 + */ + +@Repository +public class PersistService { + + @Autowired + private DynamicDataSource dynamicDataSource; + + private DataSourceService dataSourceService; + + @PostConstruct + public void init() { + dataSourceService = dynamicDataSource.getDataSource(); + + jt = getJdbcTemplate(); + tjt = getTransactionTemplate(); + } + + + public boolean checkMasterWritable() { + return dataSourceService.checkMasterWritable(); + } + + public void setBasicDataSourceService(DataSourceService dataSourceService) { + this.dataSourceService = dataSourceService; + } + + static final class ConfigInfoWrapperRowMapper implements + RowMapper { + public ConfigInfoWrapper mapRow(ResultSet rs, int rowNum) + throws SQLException { + ConfigInfoWrapper info = new ConfigInfoWrapper(); + + info.setDataId(rs.getString("data_id")); + info.setGroup(rs.getString("group_id")); + info.setTenant(rs.getString("tenant_id")); + info.setAppName(rs.getString("app_name")); + + try { + info.setContent(rs.getString("content")); + } catch (SQLException e) { + // ignore + } + try { + info.setId(rs.getLong("ID")); + } catch (SQLException e) { + // ignore + } + try { + info.setLastModified(rs.getTimestamp("gmt_modified").getTime()); + } catch (SQLException e) { + // ignore + } + try { + info.setMd5(rs.getString("md5")); + } catch (SQLException e) { + } + return info; + } + } + + static final class ConfigInfoBetaWrapperRowMapper implements + RowMapper { + public ConfigInfoBetaWrapper mapRow(ResultSet rs, int rowNum) + throws SQLException { + ConfigInfoBetaWrapper info = new ConfigInfoBetaWrapper(); + + info.setDataId(rs.getString("data_id")); + info.setGroup(rs.getString("group_id")); + info.setTenant(rs.getString("tenant_id")); + info.setAppName(rs.getString("app_name")); + info.setBetaIps(rs.getString("beta_ips")); + + try { + info.setContent(rs.getString("content")); + } catch (SQLException e) { + // ignore + } + try { + info.setId(rs.getLong("ID")); + } catch (SQLException e) { + // ignore + } + try { + info.setLastModified(rs.getTimestamp("gmt_modified").getTime()); + } catch (SQLException e) { + // ignore + } + try { + info.setMd5(rs.getString("md5")); + } catch (SQLException e) { + } + return info; + } + } + + static final class ConfigInfoTagWrapperRowMapper implements + RowMapper { + public ConfigInfoTagWrapper mapRow(ResultSet rs, int rowNum) + throws SQLException { + ConfigInfoTagWrapper info = new ConfigInfoTagWrapper(); + + info.setDataId(rs.getString("data_id")); + info.setGroup(rs.getString("group_id")); + info.setTenant(rs.getString("tenant_id")); + info.setTag(rs.getString("tag_id")); + info.setAppName(rs.getString("app_name")); + + try { + info.setContent(rs.getString("content")); + } catch (SQLException e) { + // ignore + } + try { + info.setId(rs.getLong("ID")); + } catch (SQLException e) { + // ignore + } + try { + info.setLastModified(rs.getTimestamp("gmt_modified").getTime()); + } catch (SQLException e) { + // ignore + } + try { + info.setMd5(rs.getString("md5")); + } catch (SQLException e) { + } + return info; + } + } + + static final class ConfigInfoRowMapper implements + RowMapper { + public ConfigInfo mapRow(ResultSet rs, int rowNum) throws SQLException { + ConfigInfo info = new ConfigInfo(); + + info.setDataId(rs.getString("data_id")); + info.setGroup(rs.getString("group_id")); + info.setTenant(rs.getString("tenant_id")); + info.setAppName(rs.getString("app_name")); + + try { + info.setContent(rs.getString("content")); + } catch (SQLException e) { + // ignore + } + try { + info.setMd5(rs.getString("md5")); + } catch (SQLException e) { + // ignore + } + try { + info.setId(rs.getLong("ID")); + } catch (SQLException e) { + // ignore + } + return info; + } + } + + static final class ConfigKeyRowMapper implements + RowMapper { + public ConfigKey mapRow(ResultSet rs, int rowNum) throws SQLException { + ConfigKey info = new ConfigKey(); + + info.setDataId(rs.getString("data_id")); + info.setGroup(rs.getString("group_id")); + info.setAppName(rs.getString("app_name")); + + return info; + } + } + + static final class ConfigAdvanceInfoRowMapper implements RowMapper { + public ConfigAdvanceInfo mapRow(ResultSet rs, int rowNum) throws SQLException { + ConfigAdvanceInfo info = new ConfigAdvanceInfo(); + info.setCreateTime(rs.getTimestamp("gmt_modified").getTime()); + info.setModifyTime(rs.getTimestamp("gmt_modified").getTime()); + info.setCreateUser(rs.getString("src_user")); + info.setCreateIp(rs.getString("src_ip")); + info.setDesc(rs.getString("c_desc")); + info.setUse(rs.getString("c_use")); + info.setEffect(rs.getString("effect")); + info.setType(rs.getString("type")); + info.setSchema(rs.getString("c_schema")); + return info; + } + } + + static final class ConfigInfo4BetaRowMapper implements + RowMapper { + public ConfigInfo4Beta mapRow(ResultSet rs, int rowNum) throws SQLException { + ConfigInfo4Beta info = new ConfigInfo4Beta(); + + info.setDataId(rs.getString("data_id")); + info.setGroup(rs.getString("group_id")); + info.setTenant(rs.getString("tenant_id")); + info.setAppName(rs.getString("app_name")); + info.setBetaIps(rs.getString("beta_ips")); + + try { + info.setContent(rs.getString("content")); + } catch (SQLException e) { + // ignore + } + try { + info.setId(rs.getLong("ID")); + } catch (SQLException e) { + // ignore + } + try { + info.setMd5(rs.getString("md5")); + } catch (SQLException e) { + } + return info; + } + } + + static final class ConfigInfo4TagRowMapper implements + RowMapper { + public ConfigInfo4Tag mapRow(ResultSet rs, int rowNum) throws SQLException { + ConfigInfo4Tag info = new ConfigInfo4Tag(); + + info.setDataId(rs.getString("data_id")); + info.setGroup(rs.getString("group_id")); + info.setTenant(rs.getString("tenant_id")); + info.setTag(rs.getString("tag_id")); + info.setAppName(rs.getString("app_name")); + + try { + info.setContent(rs.getString("content")); + } catch (SQLException e) { + // ignore + } + try { + info.setId(rs.getLong("ID")); + } catch (SQLException e) { + // ignore + } + try { + info.setMd5(rs.getString("md5")); + } catch (SQLException e) { + } + return info; + } + } + + static final class ConfigInfoBaseRowMapper implements + RowMapper { + public ConfigInfoBase mapRow(ResultSet rs, int rowNum) throws SQLException { + ConfigInfoBase info = new ConfigInfoBase(); + + info.setDataId(rs.getString("data_id")); + info.setGroup(rs.getString("group_id")); + + try { + info.setContent(rs.getString("content")); + } catch (SQLException e) { + // ignore + } + try { + info.setId(rs.getLong("ID")); + } catch (SQLException e) { + // ignore + } + return info; + } + } + + static final class ConfigInfoAggrRowMapper implements + RowMapper { + public ConfigInfoAggr mapRow(ResultSet rs, int rowNum) + throws SQLException { + ConfigInfoAggr info = new ConfigInfoAggr(); + info.setDataId(rs.getString("data_id")); + info.setGroup(rs.getString("group_id")); + info.setDatumId(rs.getString("datum_id")); + info.setTenant(rs.getString("tenant_id")); + info.setAppName(rs.getString("app_name")); + info.setContent(rs.getString("content")); + return info; + } + } + + static final class ConfigInfoChangedRowMapper implements RowMapper { + public ConfigInfoChanged mapRow(ResultSet rs, int rowNum) throws SQLException { + ConfigInfoChanged info = new ConfigInfoChanged(); + info.setDataId(rs.getString("data_id")); + info.setGroup(rs.getString("group_id")); + info.setTenant(rs.getString("tenant_id")); + return info; + } + } + + static final class ConfigHistoryRowMapper implements RowMapper { + public ConfigHistoryInfo mapRow(ResultSet rs, int rowNum) throws SQLException { + ConfigHistoryInfo configHistoryInfo = new ConfigHistoryInfo(); + configHistoryInfo.setId(rs.getLong("nid")); + configHistoryInfo.setDataId(rs.getString("data_id")); + configHistoryInfo.setGroup(rs.getString("group_id")); + configHistoryInfo.setTenant(rs.getString("tenant_id")); + configHistoryInfo.setAppName(rs.getString("app_name")); + configHistoryInfo.setSrcIp(rs.getString("src_ip")); + configHistoryInfo.setOpType(rs.getString("op_type")); + configHistoryInfo.setCreatedTime(rs.getTimestamp("gmt_create")); + configHistoryInfo.setLastModifiedTime(rs.getTimestamp("gmt_modified")); + return configHistoryInfo; + } + } + + static final class ConfigHistoryDetailRowMapper implements RowMapper { + public ConfigHistoryInfo mapRow(ResultSet rs, int rowNum) throws SQLException { + ConfigHistoryInfo configHistoryInfo = new ConfigHistoryInfo(); + configHistoryInfo.setId(rs.getLong("nid")); + configHistoryInfo.setDataId(rs.getString("data_id")); + configHistoryInfo.setGroup(rs.getString("group_id")); + configHistoryInfo.setTenant(rs.getString("tenant_id")); + configHistoryInfo.setAppName(rs.getString("app_name")); + configHistoryInfo.setMd5(rs.getString("md5")); + configHistoryInfo.setContent(rs.getString("content")); + configHistoryInfo.setSrcUser(rs.getString("src_user")); + configHistoryInfo.setSrcIp(rs.getString("src_ip")); + configHistoryInfo.setOpType(rs.getString("op_type")); + configHistoryInfo.setCreatedTime(rs.getTimestamp("gmt_create")); + configHistoryInfo.setLastModifiedTime(rs.getTimestamp("gmt_modified")); + return configHistoryInfo; + } + }; + + + public synchronized void reload() throws IOException { + this.dataSourceService.reload(); + } + + /** + * 单元测试用 + */ + public JdbcTemplate getJdbcTemplate() { + return this.dataSourceService.getJdbcTemplate(); + } + + public TransactionTemplate getTransactionTemplate() { + return this.dataSourceService.getTransactionTemplate(); + } + + public String getCurrentDBUrl() { + return this.dataSourceService.getCurrentDBUrl(); + } + + // ----------------------- config_info 表 insert update delete + /** + * 添加普通配置信息,发布数据变更事件 + */ + public void addConfigInfo(final String srcIp, final String srcUser, final ConfigInfo configInfo, final Timestamp time, final Map configAdvanceInfo, final boolean notify) { + tjt.execute(new TransactionCallback() { + @Override + public Boolean doInTransaction(TransactionStatus status) { + try { + long configId = addConfigInfoAtomic(srcIp, srcUser, configInfo, time, configAdvanceInfo); + String configTags = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("config_tags"); + addConfiTagsRelationAtomic(configId, configTags, configInfo.getDataId(), configInfo.getGroup(), + configInfo.getTenant()); + insertConfigHistoryAtomic(0, configInfo, srcIp, srcUser, time, "I"); + if (notify) { + EventDispatcher.fireEvent(new ConfigDataChangeEvent(false, configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant(), time.getTime())); + } + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + return Boolean.TRUE; + } + }); + } + + /** + * 添加普通配置信息,发布数据变更事件 + */ + public void addConfigInfo4Beta(ConfigInfo configInfo, String betaIps, + String srcIp, String srcUser, Timestamp time, boolean notify) { + String appNameTmp = StringUtils.isBlank(configInfo.getAppName()) ? StringUtils.EMPTY : configInfo.getAppName(); + String tenantTmp = StringUtils.isBlank(configInfo.getTenant()) ? StringUtils.EMPTY : configInfo.getTenant(); + try { + String md5 = MD5.getInstance().getMD5String(configInfo.getContent()); + jt.update( + "insert into config_info_beta(data_id,group_id,tenant_id,app_name,content,md5,beta_ips,src_ip,src_user,gmt_create,gmt_modified) values(?,?,?,?,?,?,?,?,?,?,?)", + configInfo.getDataId(), configInfo.getGroup(), tenantTmp, appNameTmp, configInfo.getContent(), md5, + betaIps, srcIp, srcUser, time, time); + if (notify) { + EventDispatcher.fireEvent(new ConfigDataChangeEvent(true, configInfo.getDataId(), configInfo.getGroup(), + tenantTmp, time.getTime())); + } + + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 添加普通配置信息,发布数据变更事件 + */ + public void addConfigInfo4Tag(ConfigInfo configInfo, String tag, String srcIp, String srcUser, Timestamp time, + boolean notify) { + String appNameTmp = StringUtils.isBlank(configInfo.getAppName()) ? StringUtils.EMPTY : configInfo.getAppName(); + String tenantTmp = StringUtils.isBlank(configInfo.getTenant()) ? StringUtils.EMPTY : configInfo.getTenant(); + String tagTmp = StringUtils.isBlank(tag) ? StringUtils.EMPTY : tag.trim(); + try { + String md5 = MD5.getInstance().getMD5String(configInfo.getContent()); + jt.update( + "insert into config_info_tag(data_id,group_id,tenant_id,tag_id,app_name,content,md5,src_ip,src_user,gmt_create,gmt_modified) values(?,?,?,?,?,?,?,?,?,?,?)", + configInfo.getDataId(), configInfo.getGroup(), tenantTmp, tagTmp, appNameTmp, configInfo.getContent(), md5, + srcIp, srcUser, time, time); + if (notify) { + EventDispatcher.fireEvent(new ConfigDataChangeEvent(false, configInfo.getDataId(), + configInfo.getGroup(), tenantTmp, tagTmp, time.getTime())); + } + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 更新配置信息 + */ + public void updateConfigInfo(final ConfigInfo configInfo, final String srcIp, final String srcUser, + final Timestamp time, final Map configAdvanceInfo, final boolean notify) { + tjt.execute(new TransactionCallback() { + @Override + public Boolean doInTransaction(TransactionStatus status) { + try { + ConfigInfo oldConfigInfo = findConfigInfo(configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant()); + String appNameTmp = oldConfigInfo.getAppName(); + // 用户传过来的appName不为空,则用持久化用户的appName,否则用db的;清空appName的时候需要传空串 + if (configInfo.getAppName() == null) { + configInfo.setAppName(appNameTmp); + } + updateConfigInfoAtomic(configInfo, srcIp, srcUser, time, configAdvanceInfo); + String configTags = configAdvanceInfo == null ? null : (String)configAdvanceInfo.get("config_tags"); + if (configTags != null) { + // 删除所有tag,然后再重新创建 + removeTagByIdAtomic(oldConfigInfo.getId()); + addConfiTagsRelationAtomic(oldConfigInfo.getId(), configTags, configInfo.getDataId(), + configInfo.getGroup(), configInfo.getTenant()); + } + insertConfigHistoryAtomic(oldConfigInfo.getId(), oldConfigInfo, srcIp, srcUser, time, "U"); + if (notify) { + EventDispatcher.fireEvent(new ConfigDataChangeEvent(false, configInfo.getDataId(), + configInfo.getGroup(), configInfo.getTenant(), time.getTime())); + } + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + return Boolean.TRUE; + } + }); + } + + /** + * 更新配置信息 + */ + public void updateConfigInfo4Beta(ConfigInfo configInfo, String srcIp, String srcUser, Timestamp time, + boolean notify) { + String appNameTmp = StringUtils.isBlank(configInfo.getAppName()) ? StringUtils.EMPTY : configInfo.getAppName(); + String tenantTmp = StringUtils.isBlank(configInfo.getTenant()) ? StringUtils.EMPTY : configInfo.getTenant(); + try { + String md5 = MD5.getInstance().getMD5String(configInfo.getContent()); + jt.update( + "update config_info_beta set content=?, md5 = ?, src_ip=?,src_user=?,gmt_modified=?,app_name=? where data_id=? and group_id=? and tenant_id=?", + configInfo.getContent(), md5, srcIp, srcUser, time, appNameTmp, configInfo.getDataId(), + configInfo.getGroup(), tenantTmp); + if (notify) { + EventDispatcher.fireEvent(new ConfigDataChangeEvent(true, configInfo.getDataId(), configInfo.getGroup(), + tenantTmp, time.getTime())); + } + + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 更新配置信息 + */ + public void updateConfigInfo4Tag(ConfigInfo configInfo, String tag, String srcIp, String srcUser, Timestamp time, + boolean notify) { + String appNameTmp = StringUtils.isBlank(configInfo.getAppName()) ? StringUtils.EMPTY : configInfo.getAppName(); + String tenantTmp = StringUtils.isBlank(configInfo.getTenant()) ? StringUtils.EMPTY : configInfo.getTenant(); + String tagTmp = StringUtils.isBlank(tag) ? StringUtils.EMPTY : tag.trim(); + try { + String md5 = MD5.getInstance().getMD5String(configInfo.getContent()); + jt.update( + "update config_info_tag set content=?, md5 = ?, src_ip=?,src_user=?,gmt_modified=?,app_name=? where data_id=? and group_id=? and tenant_id=? and tag_id=?", + configInfo.getContent(), md5, srcIp, srcUser, time, appNameTmp, configInfo.getDataId(), + configInfo.getGroup(), tenantTmp, tagTmp); + if (notify) { + EventDispatcher.fireEvent(new ConfigDataChangeEvent(true, configInfo.getDataId(), configInfo.getGroup(), + tenantTmp, tagTmp, time.getTime())); + } + + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + public void insertOrUpdateBeta(final ConfigInfo configInfo, final String betaIps, final String srcIp, + final String srcUser, final Timestamp time, final boolean notify) { + try { + addConfigInfo4Beta(configInfo, betaIps, srcIp, null, time, notify); + } catch (DataIntegrityViolationException ive) { // 唯一性约束冲突 + updateConfigInfo4Beta(configInfo, srcIp, null, time, notify); + } + } + + public void insertOrUpdateTag(final ConfigInfo configInfo, final String tag, final String srcIp, + final String srcUser, final Timestamp time, final boolean notify) { + try { + addConfigInfo4Tag(configInfo, tag, srcIp, null, time, notify); + } catch (DataIntegrityViolationException ive) { // 唯一性约束冲突 + updateConfigInfo4Tag(configInfo, tag, srcIp, null, time, notify); + } + } + + /** + * 更新md5 + */ + public void updateMd5(String dataId, String group, String tenant, String md5, Timestamp lastTime) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + try { + jt.update( + "update config_info set md5 = ? where data_id=? and group_id=? and tenant_id=? and gmt_modified=?", + md5, dataId, group, tenantTmp, lastTime); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + public void insertOrUpdate(String srcIp, String srcUser, ConfigInfo configInfo, Timestamp time, Map configAdvanceInfo) { + insertOrUpdate(srcIp, srcUser, configInfo, time, configAdvanceInfo, true); + } + + /** + * 写入主表,插入或更新 + */ + public void insertOrUpdate(String srcIp, String srcUser, ConfigInfo configInfo, Timestamp time, Map configAdvanceInfo, boolean notify) { + try { + addConfigInfo(srcIp, srcUser, configInfo, time, configAdvanceInfo, notify); + } catch (DataIntegrityViolationException ive) { // 唯一性约束冲突 + updateConfigInfo(configInfo, srcIp, srcUser, time, configAdvanceInfo, notify); + } + } + + /** + * 写入主表,插入或更新 + */ + public void insertOrUpdateSub(SubInfo subInfo) { + try { + addConfigSubAtomic(subInfo.getDataId(), subInfo.getGroup(), subInfo.getAppName(), subInfo.getDate()); + } catch (DataIntegrityViolationException ive) { // 唯一性约束冲突 + updateConfigSubAtomic(subInfo.getDataId(), subInfo.getGroup(), subInfo.getAppName(), subInfo.getDate()); + } + } + + /** + * 删除配置信息, 物理删除 + */ + public void removeConfigInfo(final String dataId, final String group, final String tenant, final String srcIp, final String srcUser) { tjt.execute(new TransactionCallback() { + final Timestamp time = new Timestamp(System.currentTimeMillis()); + @Override + public Boolean doInTransaction(TransactionStatus status) { + try { + ConfigInfo configInfo = findConfigInfo(dataId, group, tenant); + if (configInfo != null) { + removeConfigInfoAtomic(dataId, group, tenant, srcIp, srcUser); + removeTagByIdAtomic(configInfo.getId()); + insertConfigHistoryAtomic(configInfo.getId(), configInfo, srcIp, srcUser, time, "D"); + } + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + return Boolean.TRUE; + } + }); + } + + /** + * 删除beta配置信息, 物理删除 + */ + public void removeConfigInfo4Beta(final String dataId, final String group, final String tenant) { + final String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + tjt.execute(new TransactionCallback() { + @Override + public Boolean doInTransaction(TransactionStatus status) { + try { + ConfigInfo configInfo = findConfigInfo4Beta(dataId, group, tenant); + if (configInfo != null) { + jt.update("delete from config_info_beta where data_id=? and group_id=? and tenant_id=?", dataId, + group, tenantTmp); + } + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + return Boolean.TRUE; + } + }); + } + + // ----------------------- config_aggr_info 表 insert update delete + /** + * 增加聚合前数据到数据库, select -> update or insert + */ + public boolean addAggrConfigInfo(final String dataId, final String group, String tenant, final String datumId, + String appName, final String content) { + String appNameTmp = StringUtils.isBlank(appName) ? StringUtils.EMPTY : appName; + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + final Timestamp now = new Timestamp(System.currentTimeMillis()); + String select = "select content from config_info_aggr where data_id = ? and group_id = ? and tenant_id = ? and datum_id = ?"; + String insert = "insert into config_info_aggr(data_id, group_id, tenant_id, datum_id, app_name, content, gmt_modified) values(?,?,?,?,?,?,?) "; + String update = "update config_info_aggr set content = ? , gmt_modified = ? where data_id = ? and group_id = ? and tenant_id = ? and datum_id = ?"; + + try { + try { + String dbContent = jt.queryForObject(select, new Object[] { dataId, group, tenantTmp, datumId }, + String.class); + + if (dbContent != null && dbContent.equals(content)) { + return true; + } else { + return jt.update(update, content, now, dataId, group, tenantTmp, datumId) > 0; + } + } catch (EmptyResultDataAccessException ex) { // no data, insert + return jt.update(insert, dataId, group, tenantTmp, datumId, appNameTmp, content, now) > 0; + } + } catch (DataAccessException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 删除单条聚合前数据 + */ + public void removeSingleAggrConfigInfo(final String dataId, + final String group, final String tenant, final String datumId) { + final String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + String sql = "delete from config_info_aggr where data_id=? and group_id=? and tenant_id=? and datum_id=?"; + + try { + this.jt.update(sql, new PreparedStatementSetter() { + public void setValues(PreparedStatement ps) throws SQLException { + int index = 1; + ps.setString(index++, dataId); + ps.setString(index++, group); + ps.setString(index++, tenantTmp); + ps.setString(index++, datumId); + } + }); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 删除一个dataId下面所有的聚合前数据 + */ + public void removeAggrConfigInfo(final String dataId, final String group, final String tenant) { + final String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + String sql = "delete from config_info_aggr where data_id=? and group_id=? and tenant_id=?"; + + try { + this.jt.update(sql, new PreparedStatementSetter() { + public void setValues(PreparedStatement ps) throws SQLException { + int index = 1; + ps.setString(index++, dataId); + ps.setString(index++, group); + ps.setString(index++, tenantTmp); + } + }); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 批量删除聚合数据,需要指定datum的列表 + * + * @param dataId + * @param group + * @param datumList + */ + public boolean batchRemoveAggr(final String dataId, final String group, final String tenant, + final List datumList) { + final String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + final StringBuilder datumString = new StringBuilder(); + for (String datum : datumList) { + datumString.append("'").append(datum).append("',"); + } + datumString.deleteCharAt(datumString.length() - 1); + final String sql = "delete from config_info_aggr where data_id=? and group_id=? and tenant_id=? and datum_id in (" + + datumString.toString() + ")"; + try { + jt.update(sql, dataId, group, tenantTmp); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + return false; + } + return true; + } + + /** + * 删除startTime前的数据 + */ + public void removeConfigHistory(final Timestamp startTime, final int limitSize) { + String sql = "delete from his_config_info where gmt_modified < ? limit ?"; + PaginationHelper helper = new PaginationHelper(); + try { + helper.updateLimit(jt, sql, new Object[]{startTime, limitSize}); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 获取指定时间前配置条数 + */ + public int findConfigHistoryCountByTime(final Timestamp startTime) { + String sql = "select COUNT(*) from his_config_info where gmt_modified < ?"; + Integer result = jt.queryForObject(sql, Integer.class, new Object[] { startTime }); + if (result == null) { + throw new IllegalArgumentException("configInfoBetaCount error"); + } + return result.intValue(); + } + + /** + * 获取最大maxId + */ + public long findConfigMaxId() { + String sql = "select max(id) from config_info"; + try { + return jt.queryForObject(sql, Integer.class); + } catch (NullPointerException e) { + return 0; + } + } + + /** + * 批量添加或者更新数据.事务过程中出现任何异常都会强制抛出TransactionSystemException + * + * @param dataId + * @param group + * @param datumMap + * @return + */ + public boolean batchPublishAggr(final String dataId, final String group, final String tenant, + final Map datumMap, final String appName) { + try { + Boolean isPublishOk = tjt.execute(new TransactionCallback() { + @Override + public Boolean doInTransaction(TransactionStatus status) { + for (Entry entry : datumMap.entrySet()) { + try { + if (!addAggrConfigInfo(dataId, group, tenant, entry.getKey(), appName, entry.getValue())) { + throw new TransactionSystemException( + "error in addAggrConfigInfo"); + } + } catch (Throwable e) { + throw new TransactionSystemException( + "error in addAggrConfigInfo"); + } + } + return Boolean.TRUE; + } + }); + if (isPublishOk == null) { + return false; + } + return isPublishOk.booleanValue(); + } catch (TransactionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + return false; + } + } + + /** + * 批量替换,先全部删除聚合表中指定DataID+Group的数据,再插入数据. + * 事务过程中出现任何异常都会强制抛出TransactionSystemException + * + * @param dataId + * @param group + * @param datumMap + * @return + */ + public boolean replaceAggr(final String dataId, final String group, final String tenant, + final Map datumMap, final String appName) { + try { + Boolean isReplaceOk = tjt.execute(new TransactionCallback() { + @Override + public Boolean doInTransaction(TransactionStatus status) { + try { + String appNameTmp = appName == null ? "" : appName; + removeAggrConfigInfo(dataId, group, tenant); + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + String sql = "insert into config_info_aggr(data_id, group_id, tenant_id, datum_id, app_name, content, gmt_modified) values(?,?,?,?,?,?,?) "; + for (Entry datumEntry : datumMap.entrySet()) { + jt.update(sql, dataId, group, tenantTmp, datumEntry.getKey(), appNameTmp, + datumEntry.getValue(), new Timestamp(System.currentTimeMillis())); + } + } catch (Throwable e) { + throw new TransactionSystemException( + "error in addAggrConfigInfo"); + } + return Boolean.TRUE; + } + }); + if (isReplaceOk == null) { + return false; + } + return isReplaceOk.booleanValue(); + } catch (TransactionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + return false; + } + + } + + /** + * 查找所有的dataId和group。保证不返回NULL。 + */ + @Deprecated + public List findAllDataIdAndGroup() { + String sql = "select distinct data_id, group_id from config_info"; + + try { + return jt.query(sql, new Object[] {}, CONFIG_INFO_ROW_MAPPER); + } catch (EmptyResultDataAccessException e) { + return Collections.emptyList(); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } catch (Exception e) { + fatalLog.error("[db-other-error]" + e.getMessage(), e); + throw new RuntimeException(e); + } + } + + /** + * 根据dataId和group查询配置信息 + */ + public ConfigInfo4Beta findConfigInfo4Beta(final String dataId, final String group, final String tenant) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + try { + return this.jt.queryForObject( + "select ID,data_id,group_id,tenant_id,app_name,content,beta_ips from config_info_beta where data_id=? and group_id=? and tenant_id=?", + new Object[] { dataId, group, tenantTmp }, CONFIG_INFO4BETA_ROW_MAPPER); + } catch (EmptyResultDataAccessException e) { // 表明数据不存在, 返回null + return null; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 根据dataId和group查询配置信息 + */ + public ConfigInfo4Tag findConfigInfo4Tag(final String dataId, final String group, final String tenant, final String tag) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + String tagTmp = StringUtils.isBlank(tag) ? StringUtils.EMPTY : tag.trim(); + try { + return this.jt.queryForObject( + "select ID,data_id,group_id,tenant_id,tag_id,app_name,content from config_info_tag where data_id=? and group_id=? and tenant_id=? and tag_id=?", + new Object[] { dataId, group, tenantTmp, tagTmp }, CONFIG_INFO4TAG_ROW_MAPPER); + } catch (EmptyResultDataAccessException e) { // 表明数据不存在, 返回null + return null; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + + /** + * 根据dataId和group查询配置信息 + */ + public ConfigInfo findConfigInfoApp(final String dataId, final String group, final String tenant, + final String appName) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + try { + return this.jt.queryForObject( + "select ID,data_id,group_id,tenant_id,app_name,content from config_info where data_id=? and group_id=? and tenant_id=? and app_name=?", + new Object[] { dataId, group, tenantTmp, appName }, CONFIG_INFO_ROW_MAPPER); + } catch (EmptyResultDataAccessException e) { // 表明数据不存在, 返回null + return null; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 根据dataId和group查询配置信息 + */ + public ConfigInfo findConfigInfoAdvanceInfo(final String dataId, final String group, final String tenant, + final Map configAdvanceInfo) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + final String appName = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("appName"); + final String configTags = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("config_tags"); + List paramList = new ArrayList(); + paramList.add(dataId); + paramList.add(group); + paramList.add(tenantTmp); + + StringBuilder sql = new StringBuilder("select ID,data_id,group_id,tenant_id,app_name,content from config_info where data_id=? and group_id=? and tenant_id=? "); + if (StringUtils.isNotBlank(configTags)) { + sql = new StringBuilder("select a.ID,a.data_id,a.group_id,a.tenant_id,a.app_name,a.content from config_info a left join config_tags_relation b on a.id=b.id where a.data_id=? and a.group_id=? and a.tenant_id=? "); + sql.append(" and b.tag_name in ("); + String [] tagArr = configTags.split(","); + for (int i = 0; i < tagArr.length; i++) { + if (i != 0) { + sql.append(", "); + } + sql.append("?"); + paramList.add(tagArr[i]); + } + sql.append(") "); + + if (StringUtils.isNotBlank(appName)) { + sql.append(" and a.app_name=? "); + paramList.add(appName); + } + } else { + if (StringUtils.isNotBlank(appName)) { + sql.append(" and app_name=? "); + paramList.add(appName); + } + } + + try { + return this.jt.queryForObject(sql.toString(), paramList.toArray(), CONFIG_INFO_ROW_MAPPER); + } catch (EmptyResultDataAccessException e) { // 表明数据不存在, 返回null + return null; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + + } + + /** + * 根据dataId和group查询配置信息 + */ + public ConfigInfoBase findConfigInfoBase(final String dataId, final String group) { + try { + return this.jt + .queryForObject( + "select ID,data_id,group_id,content from config_info where data_id=? and group_id=? and tenant_id=?", + new Object[] { dataId, group, StringUtils.EMPTY}, + CONFIG_INFO_BASE_ROW_MAPPER); + } catch (EmptyResultDataAccessException e) { // 表明数据不存在, 返回null + return null; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + + /** + * 根据数据库主键ID查询配置信息 + * + * @param id + * @return + */ + public ConfigInfo findConfigInfo(long id) { + try { + return this.jt + .queryForObject( + "select ID,data_id,group_id,tenant_id,app_name,content from config_info where ID=?", + new Object[] { id }, CONFIG_INFO_ROW_MAPPER); + } catch (EmptyResultDataAccessException e) { // 表明数据不存在 + return null; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 根据dataId查询配置信息 + * + * @param pageNo + * 页码(必须大于0) + * @param pageSize + * 每页大小(必须大于0) + * @param dataId + * + * @return ConfigInfo对象的集合 + */ + public Page findConfigInfoByDataId(final int pageNo, final int pageSize, final String dataId, + final String tenant) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + PaginationHelper helper = new PaginationHelper(); + try { + return helper.fetchPage(this.jt, "select count(*) from config_info where data_id=? and tenant_id=?", + "select ID,data_id,group_id,tenant_id,app_name,content from config_info where data_id=? and tenant_id=?", + new Object[] { dataId, tenantTmp }, pageNo, pageSize, CONFIG_INFO_ROW_MAPPER); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 根据dataId查询配置信息 + * + * @param pageNo + * 页码(必须大于0) + * @param pageSize + * 每页大小(必须大于0) + * @param dataId + * + * @return ConfigInfo对象的集合 + */ + public Page findConfigInfoByDataIdAndApp(final int pageNo, final int pageSize, final String dataId, + final String tenant, final String appName) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + PaginationHelper helper = new PaginationHelper(); + try { + return helper.fetchPage(this.jt, + "select count(*) from config_info where data_id=? and tenant_id=? and app_name=?", + "select ID,data_id,group_id,tenant_id,app_name,content from config_info where data_id=? and tenant_id=? and app_name=?", + new Object[] { dataId, tenantTmp, appName }, pageNo, pageSize, CONFIG_INFO_ROW_MAPPER); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + public Page findConfigInfoByDataIdAndAdvance(final int pageNo, final int pageSize, final String dataId, + final String tenant, final Map configAdvanceInfo) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + PaginationHelper helper = new PaginationHelper(); + final String appName = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("appName"); + final String configTags = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("config_tags"); + StringBuilder sqlCount = new StringBuilder("select count(*) from config_info where data_id=? and tenant_id=? "); + StringBuilder sql = new StringBuilder("select ID,data_id,group_id,tenant_id,app_name,content from config_info where data_id=? and tenant_id=? "); + List paramList = new ArrayList(); + paramList.add(dataId); + paramList.add(tenantTmp); + if (StringUtils.isNotBlank(configTags)) { + sqlCount = new StringBuilder("select count(*) from config_info a left join config_tags_relation b on a.id=b.id where a.data_id=? and a.tenant_id=? "); + + sql = new StringBuilder("select a.ID,a.data_id,a.group_id,a.tenant_id,a.app_name,a.content from config_info a left join config_tags_relation b on a.id=b.id where a.data_id=? and a.tenant_id=? "); + + sqlCount.append(" and b.tag_name in ("); + sql.append(" and b.tag_name in ("); + String [] tagArr = configTags.split(","); + for (int i = 0; i < tagArr.length; i++) { + if (i != 0) { + sqlCount.append(", "); + sql.append(", "); + } + sqlCount.append("?"); + sql.append("?"); + paramList.add(tagArr[i]); + } + sqlCount.append(") "); + sql.append(") "); + + if (StringUtils.isNotBlank(appName)) { + sqlCount.append(" and a.app_name=? "); + sql.append(" and a.app_name=? "); + paramList.add(appName); + } + } else { + if (StringUtils.isNotBlank(appName)) { + sqlCount.append(" and app_name=? "); + sql.append(" and app_name=? "); + paramList.add(appName); + } + } + try { + return helper.fetchPage(this.jt, sqlCount.toString(), sql.toString(), paramList.toArray(), pageNo, pageSize, + CONFIG_INFO_ROW_MAPPER); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + public Page findConfigInfo4Page(final int pageNo, final int pageSize, final String dataId, final String group, + final String tenant, final Map configAdvanceInfo) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + PaginationHelper helper = new PaginationHelper(); + final String appName = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("appName"); + final String configTags = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("config_tags"); + String sqlCount = "select count(*) from config_info"; + String sql = "select ID,data_id,group_id,tenant_id,app_name,content from config_info"; + StringBuilder where = new StringBuilder(" where "); + List paramList = new ArrayList(); + paramList.add(tenantTmp); + if (StringUtils.isNotBlank(configTags)) { + sqlCount = "select count(*) from config_info a left join config_tags_relation b on a.id=b.id"; + sql = "select a.ID,a.data_id,a.group_id,a.tenant_id,a.app_name,a.content from config_info a left join config_tags_relation b on a.id=b.id"; + + where.append(" a.tenant_id=? "); + + if (StringUtils.isNotBlank(dataId)) { + where.append(" and a.data_id=? "); + paramList.add(dataId); + } + if (StringUtils.isNotBlank(group)) { + where.append(" and a.group_id=? "); + paramList.add(group); + } + if (StringUtils.isNotBlank(appName)) { + where.append(" and a.app_name=? "); + paramList.add(appName); + } + + where.append(" and b.tag_name in ("); + String [] tagArr= configTags.split(","); + for (int i = 0; i < tagArr.length; i++) { + if (i != 0) { + where.append(", "); + } + where.append("?"); + paramList.add(tagArr[i]); + } + where.append(") "); + } else { + where.append(" tenant_id=? "); + if (StringUtils.isNotBlank(dataId)) { + where.append(" and data_id=? "); + paramList.add(dataId); + } + if (StringUtils.isNotBlank(group)) { + where.append(" and group_id=? "); + paramList.add(group); + } + if (StringUtils.isNotBlank(appName)) { + where.append(" and app_name=? "); + paramList.add(appName); + } + } + try { + return helper.fetchPage(this.jt, sqlCount + where, sql + where, paramList.toArray(), pageNo, pageSize, + CONFIG_INFO_ROW_MAPPER); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 根据dataId查询配置信息 + * + * @param pageNo + * 页码(必须大于0) + * @param pageSize + * 每页大小(必须大于0) + * @param dataId + * + * @return ConfigInfo对象的集合 + */ + public Page findConfigInfoBaseByDataId(final int pageNo, + final int pageSize, final String dataId) { + PaginationHelper helper = new PaginationHelper(); + try { + return helper + .fetchPage( + this.jt, + "select count(*) from config_info where data_id=? and tenant_id=?", + "select ID,data_id,group_id,content from config_info where data_id=? and tenant_id=?", + new Object[] { dataId, StringUtils.EMPTY }, pageNo, pageSize, + CONFIG_INFO_BASE_ROW_MAPPER); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 根据group查询配置信息 + * + * @param pageNo + * 页码(必须大于0) + * @param pageSize + * 每页大小(必须大于0) + * + * @param group + * + * @return ConfigInfo对象的集合 + */ + public Page findConfigInfoByGroup(final int pageNo, final int pageSize, final String group, + final String tenant) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + PaginationHelper helper = new PaginationHelper(); + try { + return helper.fetchPage(this.jt, "select count(*) from config_info where group_id=? and tenant_id=?", + "select ID,data_id,group_id,tenant_id,app_name,content from config_info where group_id=? and tenant_id=?", + new Object[] { group, tenantTmp }, pageNo, pageSize, CONFIG_INFO_ROW_MAPPER); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + /** + * 根据group查询配置信息 + * + * @param pageNo + * 页码(必须大于0) + * @param pageSize + * 每页大小(必须大于0) + * + * @param group + * + * @return ConfigInfo对象的集合 + */ + public Page findConfigInfoByGroupAndApp(final int pageNo, + final int pageSize, final String group, final String tenant, final String appName) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + PaginationHelper helper = new PaginationHelper(); + try { + return helper.fetchPage(this.jt, + "select count(*) from config_info where group_id=? and tenant_id=? and app_name =?", + "select ID,data_id,group_id,tenant_id,app_name,content from config_info where group_id=? and tenant_id=? and app_name =?", + new Object[] { group, tenantTmp, appName }, pageNo, pageSize, CONFIG_INFO_ROW_MAPPER); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + public Page findConfigInfoByGroupAndAdvance(final int pageNo, + final int pageSize, final String group, final String tenant, final Map configAdvanceInfo) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + PaginationHelper helper = new PaginationHelper(); + + final String appName = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("appName"); + final String configTags = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("config_tags"); + StringBuilder sqlCount = new StringBuilder("select count(*) from config_info where group_id=? and tenant_id=? "); + StringBuilder sql = new StringBuilder("select ID,data_id,group_id,tenant_id,app_name,content from config_info where group_id=? and tenant_id=? "); + List paramList = new ArrayList(); + paramList.add(group); + paramList.add(tenantTmp); + if (StringUtils.isNotBlank(configTags)) { + sqlCount = new StringBuilder("select count(*) from config_info a left join config_tags_relation b on a.id=b.id where a.group_id=? and a.tenant_id=? "); + sql = new StringBuilder("select a.ID,a.data_id,a.group_id,a.tenant_id,a.app_name,a.content from config_info a left join config_tags_relation b on a.id=b.id where a.group_id=? and a.tenant_id=? "); + + sqlCount.append(" and b.tag_name in ("); + sql.append(" and b.tag_name in ("); + String [] tagArr = configTags.split(","); + for (int i = 0; i < tagArr.length; i++) { + if (i != 0) { + sqlCount.append(", "); + sql.append(", "); + } + sqlCount.append("?"); + sql.append("?"); + paramList.add(tagArr[i]); + } + sqlCount.append(") "); + sql.append(") "); + + if (StringUtils.isNotBlank(appName)) { + sqlCount.append(" and a.app_name=? "); + sql.append(" and a.app_name=? "); + paramList.add(appName); + } + } else { + if (StringUtils.isNotBlank(appName)) { + sqlCount.append(" and app_name=? "); + sql.append(" and app_name=? "); + paramList.add(appName); + } + } + + try { + return helper.fetchPage(this.jt, sqlCount.toString(), sql.toString(), paramList.toArray(), pageNo, pageSize, + CONFIG_INFO_ROW_MAPPER); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 根据group查询配置信息 + * + * @param pageNo + * 页码(必须大于0) + * @param pageSize + * 每页大小(必须大于0) + * + * @param group + * + * @return ConfigInfo对象的集合 + */ + public Page findConfigInfoByApp(final int pageNo, + final int pageSize, final String tenant, final String appName) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + PaginationHelper helper = new PaginationHelper(); + try { + return helper.fetchPage(this.jt, "select count(*) from config_info where tenant_id like ? and app_name=?", + "select ID,data_id,group_id,tenant_id,app_name,content from config_info where tenant_id like ? and app_name=?", + new Object[] { generateLikeArgument(tenantTmp), appName }, pageNo, pageSize, + CONFIG_INFO_ROW_MAPPER); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + public Page findConfigInfoByAdvance(final int pageNo, + final int pageSize, final String tenant, final Map configAdvanceInfo) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + PaginationHelper helper = new PaginationHelper(); + final String appName = configAdvanceInfo == null ? null : (String)configAdvanceInfo.get("appName"); + final String configTags = configAdvanceInfo == null ? null : (String)configAdvanceInfo.get("config_tags"); + StringBuilder sqlCount = new StringBuilder("select count(*) from config_info where tenant_id like ? "); + StringBuilder sql = new StringBuilder("select ID,data_id,group_id,tenant_id,app_name,content from config_info where tenant_id like ? "); + List paramList = new ArrayList(); + paramList.add(tenantTmp); + if (StringUtils.isNotBlank(configTags)) { + sqlCount = new StringBuilder("select count(*) from config_info a left join config_tags_relation b on a.id=b.id where a.tenant_id=? "); + + sql = new StringBuilder("select a.ID,a.data_id,a.group_id,a.tenant_id,a.app_name,a.content from config_info a left join config_tags_relation b on a.id=b.id where a.tenant_id=? "); + + sqlCount.append(" and b.tag_name in ("); + sql.append(" and b.tag_name in ("); + String [] tagArr = configTags.split(","); + for (int i = 0; i < tagArr.length; i++) { + if (i != 0) { + sqlCount.append(", "); + sql.append(", "); + } + sqlCount.append("?"); + sql.append("?"); + paramList.add(tagArr[i]); + } + sqlCount.append(") "); + sql.append(") "); + + if (StringUtils.isNotBlank(appName)) { + sqlCount.append(" and a.app_name=? "); + sql.append(" and a.app_name=? "); + paramList.add(appName); + } + } else { + if (StringUtils.isNotBlank(appName)) { + sqlCount.append(" and app_name=? "); + sql.append(" and app_name=? "); + paramList.add(appName); + } + } + + try { + return helper.fetchPage(this.jt, sqlCount.toString(), sql.toString(), paramList.toArray(), pageNo, pageSize, + CONFIG_INFO_ROW_MAPPER); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 根据group查询配置信息 + * + * @param pageNo + * 页码(必须大于0) + * @param pageSize + * 每页大小(必须大于0) + * + * @param group + * + * @return ConfigInfo对象的集合 + */ + public Page findConfigInfoBaseByGroup(final int pageNo, + final int pageSize, final String group) { + PaginationHelper helper = new PaginationHelper(); + try { + return helper + .fetchPage( + this.jt, + "select count(*) from config_info where group_id=? and tenant_id=?", + "select ID,data_id,group_id,content from config_info where group_id=? and tenant_id=?", + new Object[] { group, StringUtils.EMPTY }, pageNo, pageSize, + CONFIG_INFO_BASE_ROW_MAPPER); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 返回配置项个数 + */ + public int configInfoCount() { + String sql = " SELECT COUNT(ID) FROM config_info "; + Integer result = jt.queryForObject(sql, Integer.class); + if (result ==null) { + throw new IllegalArgumentException("configInfoCount error"); + } + return result.intValue(); + } + + /** + * 返回配置项个数 + */ + public int configInfoCount(String tenant) { + String sql = " SELECT COUNT(ID) FROM config_info where tenant_id like '" + tenant + "'"; + Integer result = jt.queryForObject(sql,Integer.class); + if (result ==null) { + throw new IllegalArgumentException("configInfoCount error"); + } + return result.intValue(); + } + + /** + * 返回beta配置项个数 + */ + public int configInfoBetaCount() { + String sql = " SELECT COUNT(ID) FROM config_info_beta "; + Integer result = jt.queryForObject(sql,Integer.class); + if (result == null) { + throw new IllegalArgumentException("configInfoBetaCount error"); + } + return result.intValue(); + } + + /** + * 返回beta配置项个数 + */ + public int configInfoTagCount() { + String sql = " SELECT COUNT(ID) FROM config_info_tag "; + Integer result = jt.queryForObject(sql,Integer.class); + if (result == null) { + throw new IllegalArgumentException("configInfoBetaCount error"); + } + return result.intValue(); + } + + public List getTenantIdList(int page, int pageSize) { + String sql = "select tenant_id from config_info where tenant_id != '' group by tenant_id limit ?, ?"; + int from = (page - 1) * pageSize; + return jt.queryForList(sql, String.class, from, pageSize); + } + + public List getGroupIdList(int page, int pageSize) { + String sql = "select group_id from config_info where tenant_id ='' group by group_id limit ?, ?"; + int from = (page - 1) * pageSize; + return jt.queryForList(sql, String.class, from, pageSize); + } + + public int aggrConfigInfoCount(String dataId, String group, String tenant) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + String sql = " SELECT COUNT(ID) FROM config_info_aggr WHERE data_id = ? and group_id = ? and tenant_id = ?"; + Integer result = jt.queryForObject(sql, Integer.class, new Object[] { dataId, group, tenantTmp }); + if (result == null) { + throw new IllegalArgumentException("aggrConfigInfoCount error"); + } + return result.intValue(); + } + + public int aggrConfigInfoCountIn(String dataId, String group, String tenant, List datumIds) { + return aggrConfigInfoCount(dataId, group, tenant, datumIds, true); + } + + public int aggrConfigInfoCountNotIn(String dataId, String group, String tenant, List datumIds) { + return aggrConfigInfoCount(dataId, group, tenant, datumIds, false); + } + + private int aggrConfigInfoCount(String dataId, String group, String tenant, List datumIds, + boolean isIn) { + if (datumIds == null || datumIds.isEmpty()) { + return 0; + } + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + StringBuilder sql = new StringBuilder( + " SELECT COUNT(*) FROM config_info_aggr WHERE data_id = ? and group_id = ? and tenant_id = ? and " + + "datum_id"); + if (isIn) { + sql.append(" in ("); + } else { + sql.append(" not in ("); + } + for (int i = 0, size = datumIds.size(); i < size; i++) { + if (i > 0) { + sql.append(", "); + } + sql.append("?"); + } + sql.append(")"); + + List objectList = Lists.newArrayList(dataId, group, tenantTmp); + objectList.addAll(datumIds); + Integer result = jt.queryForObject(sql.toString(), Integer.class, objectList.toArray()); + if (result == null) { + throw new IllegalArgumentException("aggrConfigInfoCount error"); + } + return result.intValue(); + } + + /** + * 分页查询所有的配置信息 + * + * @param pageNo + * 页码(从1开始) + * @param pageSize + * 每页大小(必须大于0) + * + * @return ConfigInfo对象的集合 + */ + public Page findAllConfigInfo(final int pageNo, final int pageSize, final String tenant) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + String sqlCountRows = "SELECT COUNT(*) FROM config_info"; + String sqlFetchRows = " SELECT t.id,data_id,group_id,tenant_id,app_name,content,md5 " + + " FROM ( " + + " SELECT id FROM config_info " + + " WHERE tenant_id like ? " + + " ORDER BY id LIMIT ?,? " + + " ) g, config_info t " + + " WHERE g.id = t.id "; + + PaginationHelper helper = new PaginationHelper(); + try { + return helper.fetchPageLimit(jt, sqlCountRows, sqlFetchRows, new Object[] { generateLikeArgument(tenantTmp), (pageNo - 1) * pageSize, pageSize }, + pageNo, pageSize, CONFIG_INFO_ROW_MAPPER); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 分页查询所有的配置信息 + * + * @param pageNo + * 页码(从1开始) + * @param pageSize + * 每页大小(必须大于0) + * + * @return ConfigInfo对象的集合 + */ + public Page findAllConfigKey(final int pageNo, final int pageSize, final String tenant) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + String select = " SELECT data_id,group_id,app_name " + + " FROM ( " + + " SELECT id FROM config_info " + + " WHERE tenant_id like ? " + + " ORDER BY id LIMIT ?, ? " + + " ) g, config_info t " + + " WHERE g.id = t.id "; + + final int totalCount = configInfoCount(tenant); + int pageCount = totalCount / pageSize; + if (totalCount > pageSize * pageCount) { + pageCount++; + } + + if (pageNo > pageCount) { + return null; + } + + final Page page = new Page(); + page.setPageNumber(pageNo); + page.setPagesAvailable(pageCount); + page.setTotalCount(totalCount); + + try { + List result = jt.query(select, new Object[] { generateLikeArgument(tenantTmp), (pageNo - 1) * pageSize, pageSize }, + // new Object[0], + CONFIG_KEY_ROW_MAPPER); + + for (ConfigKey item : result) { + page.getPageItems().add(item); + } + return page; + } catch (EmptyResultDataAccessException e) { + return page; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 分页查询所有的配置信息 + * + * @param pageNo + * 页码(从1开始) + * @param pageSize + * 每页大小(必须大于0) + * + * @return ConfigInfo对象的集合 + */ + @Deprecated + public Page findAllConfigInfoBase(final int pageNo, + final int pageSize) { + String sqlCountRows = "SELECT COUNT(*) FROM config_info"; + String sqlFetchRows = " SELECT t.id,data_id,group_id,content,md5 " + + " FROM ( " + + " SELECT id FROM config_info " + + " ORDER BY id LIMIT ?,? " + + " ) g, config_info t " + + " WHERE g.id = t.id "; + + PaginationHelper helper = new PaginationHelper(); + try { + return helper.fetchPageLimit(jt, sqlCountRows, sqlFetchRows, new Object[] { + (pageNo - 1) * pageSize, pageSize }, pageNo, pageSize, CONFIG_INFO_BASE_ROW_MAPPER); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + public static class ConfigInfoWrapper extends ConfigInfo { + private static final long serialVersionUID = 4511997359365712505L; + + private long lastModified; + + public ConfigInfoWrapper() { + } + + public long getLastModified() { + return lastModified; + } + + public void setLastModified(long lastModified) { + this.lastModified = lastModified; + } + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } + } + public static class ConfigInfoBetaWrapper extends ConfigInfo4Beta { + private static final long serialVersionUID = 4511997359365712505L; + + private long lastModified; + + public ConfigInfoBetaWrapper() { + } + + public long getLastModified() { + return lastModified; + } + + public void setLastModified(long lastModified) { + this.lastModified = lastModified; + } + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } + } + + public static class ConfigInfoTagWrapper extends ConfigInfo4Tag { + private static final long serialVersionUID = 4511997359365712505L; + + private long lastModified; + + public ConfigInfoTagWrapper() { + } + + public long getLastModified() { + return lastModified; + } + + public void setLastModified(long lastModified) { + this.lastModified = lastModified; + } + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } + } + + public Page findAllConfigInfoForDumpAll( + final int pageNo, final int pageSize) { + String sqlCountRows = "select count(*) from config_info"; + String sqlFetchRows = " SELECT t.id,data_id,group_id,tenant_id,app_name,content,md5,gmt_modified " + + " FROM ( " + + " SELECT id FROM config_info " + + " ORDER BY id LIMIT ?,? " + + " ) g, config_info t " + + " WHERE g.id = t.id "; + PaginationHelper helper = new PaginationHelper(); + + List params = new ArrayList(); + + try { + return helper.fetchPageLimit(jt, sqlCountRows, sqlFetchRows, params.toArray(), pageNo, pageSize, CONFIG_INFO_WRAPPER_ROW_MAPPER); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + public Page findAllConfigInfoFragment(final long lastMaxId, final int pageSize) { + String select = "SELECT id,data_id,group_id,tenant_id,app_name,content,md5,gmt_modified from config_info where id > ? order by id asc limit ?,?"; + PaginationHelper helper = new PaginationHelper(); + try { + return helper.fetchPageLimit(jt, select, new Object[] {lastMaxId, 0, pageSize }, 1, pageSize, CONFIG_INFO_WRAPPER_ROW_MAPPER); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + public Page findAllConfigInfoBetaForDumpAll( + final int pageNo, final int pageSize) { + String sqlCountRows = "SELECT COUNT(*) FROM config_info_beta"; + String sqlFetchRows = " SELECT t.id,data_id,group_id,tenant_id,app_name,content,md5,gmt_modified,beta_ips " + + " FROM ( " + + " SELECT id FROM config_info_beta " + + " ORDER BY id LIMIT ?,? " + + " ) g, config_info_beta t " + + " WHERE g.id = t.id "; + PaginationHelper helper = new PaginationHelper(); + try { + return helper.fetchPageLimit(jt, sqlCountRows, sqlFetchRows, new Object[] { + (pageNo - 1) * pageSize, pageSize }, pageNo, pageSize, CONFIG_INFO_BETA_WRAPPER_ROW_MAPPER); + + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + public Page findAllConfigInfoTagForDumpAll( + final int pageNo, final int pageSize) { + String sqlCountRows = "SELECT COUNT(*) FROM config_info_tag"; + String sqlFetchRows = " SELECT t.id,data_id,group_id,tenant_id,tag_id,app_name,content,md5,gmt_modified " + + " FROM ( " + + " SELECT id FROM config_info_tag " + + " ORDER BY id LIMIT ?,? " + + " ) g, config_info_tag t " + + " WHERE g.id = t.id "; + PaginationHelper helper = new PaginationHelper(); + try { + return helper.fetchPageLimit(jt, sqlCountRows, sqlFetchRows, new Object[] { + (pageNo - 1) * pageSize, pageSize }, pageNo, pageSize, CONFIG_INFO_TAG_WRAPPER_ROW_MAPPER); + + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 通过select in方式实现db记录的批量查询; subQueryLimit指定in中条件的个数,上限20 + */ + public List findConfigInfoByBatch(final List dataIds, + final String group, final String tenant, int subQueryLimit) { + // assert dataids group not null + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + // if dataids empty return empty list + if (CollectionUtils.isEmpty(dataIds)) { + return Collections.emptyList(); + } + + // 批量查询上限 + // in 个数控制在100内, sql语句长度越短越好 + if (subQueryLimit > QUERY_LIMIT_SIZE) { + subQueryLimit = 50; + } + List result = new ArrayList(dataIds.size()); + + String sqlStart = "select data_id, group_id, tenant_id, app_name, content from config_info where group_id = ? and tenant_id = ? and data_id in ("; + String sqlEnd = ")"; + StringBuilder subQuerySql = new StringBuilder(); + + for (int i = 0; i < dataIds.size(); i += subQueryLimit) { + // dataids + List params = new ArrayList(dataIds.subList(i, i + + subQueryLimit < dataIds.size() ? i + subQueryLimit + : dataIds.size())); + + for (int j = 0; j < params.size(); j++) { + subQuerySql.append("?"); + if (j != params.size() - 1) { + subQuerySql.append(","); + } + } + + // group + params.add(0, group); + params.add(1, tenantTmp); + + List r = this.jt.query( + sqlStart + subQuerySql.toString() + sqlEnd, + params.toArray(), CONFIG_INFO_ROW_MAPPER); + + // assert not null + if (r != null && r.size() > 0) { + result.addAll(r); + } + } + return result; + } + + /** + * 根据dataId和group模糊查询配置信息 + * + * @param pageNo + * 页码(必须大于0) + * @param pageSize + * 每页大小(必须大于0) + * @param dataId + * 支持模糊查询 + * @param group + * 支持模糊查询 + * @param tenant + * 支持模糊查询 + * + * @return ConfigInfo对象的集合 + */ + public Page findConfigInfoLike(final int pageNo, final int pageSize, final String dataId, + final String group, final String tenant, final String appName, final String content) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + if (StringUtils.isBlank(dataId) && StringUtils.isBlank(group)) { + if (StringUtils.isBlank(appName)) { + return this.findAllConfigInfo(pageNo, pageSize, tenantTmp); + } else { + return this.findConfigInfoByApp(pageNo, pageSize, tenantTmp, appName); + } + } + + PaginationHelper helper = new PaginationHelper(); + + String sqlCountRows = "select count(*) from config_info where "; + String sqlFetchRows = "select ID,data_id,group_id,tenant_id,app_name,content from config_info where "; + String where = " 1=1 "; + List params = new ArrayList(); + + if (!StringUtils.isBlank(dataId)) { + where += " and data_id like ? "; + params.add(generateLikeArgument(dataId)); + } + if (!StringUtils.isBlank(group)) { + where += " and group_id like ? "; + params.add(generateLikeArgument(group)); + } + + where += " and tenant_id like ? "; + params.add(generateLikeArgument(tenantTmp)); + + if (!StringUtils.isBlank(appName)) { + where += " and app_name = ? "; + params.add(appName); + } + if (!StringUtils.isBlank(content)) { + where += " and content like ? "; + params.add(generateLikeArgument(content)); + } + + try { + return helper.fetchPage(jt, sqlCountRows + where, sqlFetchRows + + where, params.toArray(), pageNo, pageSize, + CONFIG_INFO_ROW_MAPPER); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + public Page findConfigInfoLike4Page(final int pageNo, final int pageSize, final String dataId, + final String group, final String tenant, final Map configAdvanceInfo) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + final String appName = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("appName"); + final String content = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("content"); + final String configTags = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("config_tags"); + PaginationHelper helper = new PaginationHelper(); + String sqlCountRows = "select count(*) from config_info"; + String sqlFetchRows = "select ID,data_id,group_id,tenant_id,app_name,content from config_info"; + StringBuilder where = new StringBuilder(" where "); + List params = new ArrayList(); + params.add(generateLikeArgument(tenantTmp)); + if (StringUtils.isNotBlank(configTags)) { + sqlCountRows = "select count(*) from config_info a left join config_tags_relation b on a.id=b.id "; + sqlFetchRows = "select a.ID,a.data_id,a.group_id,a.tenant_id,a.app_name,a.content from config_info a left join config_tags_relation b on a.id=b.id "; + + where.append(" a.tenant_id like ? "); + if (!StringUtils.isBlank(dataId)) { + where.append(" and a.data_id like ? "); + params.add(generateLikeArgument(dataId)); + } + if (!StringUtils.isBlank(group)) { + where.append(" and a.group_id like ? "); + params.add(generateLikeArgument(group)); + } + if (!StringUtils.isBlank(appName)) { + where.append(" and a.app_name = ? "); + params.add(appName); + } + if (!StringUtils.isBlank(content)) { + where.append(" and a.content like ? "); + params.add(generateLikeArgument(content)); + } + + where.append(" and b.tag_name in ("); + String[] tagArr = configTags.split(","); + for (int i = 0; i < tagArr.length; i++) { + if (i != 0) { + where.append(", "); + } + where.append("?"); + params.add(tagArr[i]); + } + where.append(") "); + } else { + where.append(" tenant_id like ? "); + if (!StringUtils.isBlank(dataId)) { + where.append(" and data_id like ? "); + params.add(generateLikeArgument(dataId)); + } + if (!StringUtils.isBlank(group)) { + where.append(" and group_id like ? "); + params.add(generateLikeArgument(group)); + } + if (!StringUtils.isBlank(appName)) { + where.append(" and app_name = ? "); + params.add(appName); + } + if (!StringUtils.isBlank(content)) { + where.append(" and content like ? "); + params.add(generateLikeArgument(content)); + } + } + + try { + return helper.fetchPage(jt, sqlCountRows + where, sqlFetchRows + + where, params.toArray(), pageNo, pageSize, + CONFIG_INFO_ROW_MAPPER); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 根据dataId和group模糊查询配置信息 + * + * @param pageNo + * 页码(必须大于0) + * @param pageSize + * 每页大小(必须大于0) + * @param configKeys + * 查询配置列表 + * @param blacklist + * 是否黑名单 + * + * @return ConfigInfo对象的集合 + */ + public Page findConfigInfoLike(final int pageNo, + final int pageSize, final ConfigKey[] configKeys, final boolean blacklist) { + String sqlCountRows = "select count(*) from config_info where "; + String sqlFetchRows = "select ID,data_id,group_id,tenant_id,app_name,content from config_info where "; + String where = " 1=1 "; + // 白名单,请同步条件为空,则没有符合条件的配置 + if (configKeys.length == 0 && blacklist == false) { + Page page = new Page(); + page.setTotalCount(0); + return page; + } + PaginationHelper helper = new PaginationHelper(); + List params = new ArrayList(); + boolean isFirst = true; + for (ConfigKey configInfo : configKeys) { + String dataId = configInfo.getDataId(); + String group = configInfo.getGroup(); + String appName = configInfo.getAppName(); + + if (StringUtils.isBlank(dataId) + && StringUtils.isBlank(group) + && StringUtils.isBlank(appName)) { + break; + } + + if (blacklist) { + if (isFirst) { + isFirst = false; + where += " and "; + } else { + where += " and "; + } + + where += "("; + boolean isFirstSub = true; + if (!StringUtils.isBlank(dataId)) { + where += " data_id not like ? "; + params.add(generateLikeArgument(dataId)); + isFirstSub = false; + } + if (!StringUtils.isBlank(group)) { + if (!isFirstSub) { + where += " or "; + } + where += " group_id not like ? "; + params.add(generateLikeArgument(group)); + isFirstSub = false; + } + if (!StringUtils.isBlank(appName)) { + if (!isFirstSub) { + where += " or "; + } + where += " app_name != ? "; + params.add(appName); + isFirstSub = false; + } + where += ") "; + } else { + if (isFirst) { + isFirst = false; + where += " and "; + } else { + where += " or "; + } + where += "("; + boolean isFirstSub = true; + if (!StringUtils.isBlank(dataId)) { + where += " data_id like ? "; + params.add(generateLikeArgument(dataId)); + isFirstSub = false; + } + if (!StringUtils.isBlank(group)) { + if (!isFirstSub) { + where += " and "; + } + where += " group_id like ? "; + params.add(generateLikeArgument(group)); + isFirstSub = false; + } + if (!StringUtils.isBlank(appName)) { + if (!isFirstSub) { + where += " and "; + } + where += " app_name = ? "; + params.add(appName); + isFirstSub = false; + } + where += ") "; + } + } + + try { + return helper.fetchPage(jt, sqlCountRows + where, sqlFetchRows + + where, params.toArray(), pageNo, pageSize, + CONFIG_INFO_ROW_MAPPER); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + /** + * 根据dataId和group模糊查询配置信息 + * + * @param pageNo + * 页码(必须大于0) + * @param pageSize + * 每页大小(必须大于0) + * @param dataId + * @param group + * + * @return ConfigInfo对象的集合 + * @throws IOException + */ + public Page findConfigInfoBaseLike(final int pageNo, + final int pageSize, final String dataId, final String group, + final String content) throws IOException { + if (StringUtils.isBlank(dataId) && StringUtils.isBlank(group)) { + throw new IOException("invalid param"); + } + + PaginationHelper helper = new PaginationHelper(); + + String sqlCountRows = "select count(*) from config_info where "; + String sqlFetchRows = "select ID,data_id,group_id,tenant_id,content from config_info where "; + String where = " 1=1 and tenant_id='' "; + List params = new ArrayList(); + + if (!StringUtils.isBlank(dataId)) { + where += " and data_id like ? "; + params.add(generateLikeArgument(dataId)); + } + if (!StringUtils.isBlank(group)) { + where += " and group_id like ? "; + params.add(generateLikeArgument(group)); + } + if (!StringUtils.isBlank(content)) { + where += " and content like ? "; + params.add(generateLikeArgument(content)); + } + + try { + return helper.fetchPage(jt, sqlCountRows + where, sqlFetchRows + + where, params.toArray(), pageNo, pageSize, + CONFIG_INFO_BASE_ROW_MAPPER); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 查找聚合前的单条数据 + * + * @param dataId + * @param group + * @param datumId + * @return + */ + public ConfigInfoAggr findSingleConfigInfoAggr(String dataId, String group, String tenant, String datumId) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + String sql = "select id,data_id,group_id,tenant_id,datum_id,app_name,content from config_info_aggr where data_id=? and group_id=? and tenant_id=? and datum_id=?"; + + try { + return this.jt.queryForObject(sql, new Object[] { dataId, group, tenantTmp, datumId }, + CONFIG_INFO_AGGR_ROW_MAPPER); + } catch (EmptyResultDataAccessException e) { + // 是EmptyResultDataAccessException, 表明数据不存在, 返回null + return null; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } catch (Exception e) { + fatalLog.error("[db-other-error]" + e.getMessage(), e); + throw new RuntimeException(e); + } + } + + /** + * 查找一个dataId下面的所有聚合前的数据. 保证不返回NULL. + */ + public List findConfigInfoAggr(String dataId, String group, String tenant) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + String sql = "select data_id,group_id,tenant_id,datum_id,app_name,content from config_info_aggr where data_id=? and group_id=? and tenant_id=? order by datum_id"; + + try { + return this.jt.query(sql, new Object[] { dataId, group, tenantTmp }, + CONFIG_INFO_AGGR_ROW_MAPPER); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } catch (EmptyResultDataAccessException e) { + return Collections.emptyList(); + } catch (Exception e) { + fatalLog.error("[db-other-error]" + e.getMessage(), e); + throw new RuntimeException(e); + } + } + + public Page findConfigInfoAggrByPage(String dataId, String group, String tenant, final int pageNo, + final int pageSize) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + String sqlCountRows = "SELECT COUNT(*) FROM config_info_aggr WHERE data_id = ? and group_id = ? and tenant_id = ?"; + String sqlFetchRows = "select data_id,group_id,tenant_id,datum_id,app_name,content from config_info_aggr where data_id=? and group_id=? and tenant_id=? order by datum_id limit ?,?"; + PaginationHelper helper = new PaginationHelper(); + try { + return helper.fetchPageLimit(jt, sqlCountRows, new Object[] {dataId, group, tenantTmp}, sqlFetchRows, new Object[] {dataId, group, tenantTmp, (pageNo - 1) * pageSize, pageSize }, + pageNo, pageSize, CONFIG_INFO_AGGR_ROW_MAPPER); + + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 查询符合条件的聚合数据 + * + * @param pageNo + * pageNo + * @param pageSize + * pageSize + * @param configKeys + * 聚合数据条件 + * @param blacklist + * 黑名单 + * @return + */ + public Page findConfigInfoAggrLike(final int pageNo, final int pageSize, ConfigKey[] configKeys, boolean blacklist) { + + String sqlCountRows = "select count(*) from config_info_aggr where "; + String sqlFetchRows = "select data_id,group_id,tenant_id,datum_id,app_name,content from config_info_aggr where "; + String where = " 1=1 "; + // 白名单,请同步条件为空,则没有符合条件的配置 + if (configKeys.length == 0 && blacklist == false) { + Page page = new Page(); + page.setTotalCount(0); + return page; + } + PaginationHelper helper = new PaginationHelper(); + List params = new ArrayList(); + boolean isFirst = true; + + for (ConfigKey configInfoAggr : configKeys) { + String dataId = configInfoAggr.getDataId(); + String group = configInfoAggr.getGroup(); + String appName = configInfoAggr.getAppName(); + if (StringUtils.isBlank(dataId) + && StringUtils.isBlank(group) + && StringUtils.isBlank(appName)) { + break; + } + if (blacklist) { + if (isFirst) { + isFirst = false; + where += " and "; + } else { + where += " and "; + } + + where += "("; + boolean isFirstSub = true; + if (!StringUtils.isBlank(dataId)) { + where += " data_id not like ? "; + params.add(generateLikeArgument(dataId)); + isFirstSub = false; + } + if (!StringUtils.isBlank(group)) { + if (!isFirstSub) { + where += " or "; + } + where += " group_id not like ? "; + params.add(generateLikeArgument(group)); + isFirstSub = false; + } + if (!StringUtils.isBlank(appName)) { + if (!isFirstSub) { + where += " or "; + } + where += " app_name != ? "; + params.add(appName); + isFirstSub = false; + } + where += ") "; + } else { + if (isFirst) { + isFirst = false; + where += " and "; + } else { + where += " or "; + } + where += "("; + boolean isFirstSub = true; + if (!StringUtils.isBlank(dataId)) { + where += " data_id like ? "; + params.add(generateLikeArgument(dataId)); + isFirstSub = false; + } + if (!StringUtils.isBlank(group)) { + if (!isFirstSub) { + where += " and "; + } + where += " group_id like ? "; + params.add(generateLikeArgument(group)); + isFirstSub = false; + } + if (!StringUtils.isBlank(appName)) { + if (!isFirstSub) { + where += " and "; + } + where += " app_name = ? "; + params.add(appName); + isFirstSub = false; + } + where += ") "; + } + } + + try { + Page result = helper.fetchPage(jt, sqlCountRows + + where, sqlFetchRows + where, params.toArray(), pageNo, + pageSize, CONFIG_INFO_AGGR_ROW_MAPPER); + return result; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 找到所有聚合数据组。 + */ + public List findAllAggrGroup() { + String sql = "select distinct data_id, group_id, tenant_id from config_info_aggr"; + + try { + return this.jt.query(sql, new Object[] {}, + CONFIG_INFO_CHANGED_ROW_MAPPER); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } catch (EmptyResultDataAccessException e) { + return null; + } catch (Exception e) { + fatalLog.error("[db-other-error]" + e.getMessage(), e); + throw new RuntimeException(e); + } + } + + /** + * 由datum内容查找datumId + * @param dataId data id + * @param groupId group + * @param content content + * @return datum keys + */ + public List findDatumIdByContent(String dataId, String groupId, + String content) { + String sql = "select datum_id from config_info_aggr where data_id = ? and group_id = ? and content = ? "; + + try { + return this.jt.queryForList(sql, new Object[] { dataId, groupId, + content }, String.class); + } catch (EmptyResultDataAccessException e) { + return null; + } catch (IncorrectResultSizeDataAccessException e) { + return null; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + public List findChangeConfig(final Timestamp startTime, + final Timestamp endTime) { + try { + List> list = jt + .queryForList( + "SELECT data_id, group_id, tenant_id, app_name, content, gmt_modified FROM config_info where gmt_modified >=? and gmt_modified <= ?", + new Object[] { startTime, endTime }); + return convertChangeConfig(list); + } catch (DataAccessException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 根据时间段和配置条件查询符合条件的配置 + * + * @param dataId + * dataId 支持模糊 + * @param group + * dataId 支持模糊 + * @param appName + * 产品名 + * @param startTime + * 起始时间 + * @param endTime + * 截止时间 + * @param pageNo + * pageNo + * @param pageSize + * pageSize + * @return + */ + public Page findChangeConfig(final String dataId, final String group, final String tenant, + final String appName, final Timestamp startTime, final Timestamp endTime, final int pageNo, + final int pageSize, final long lastMaxId) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + String sqlCountRows = "select count(*) from config_info where "; + String sqlFetchRows = "select id,data_id,group_id,tenant_id,app_name,content,md5,gmt_modified from config_info where "; + String where = " 1=1 "; + List params = new ArrayList(); + + if (!StringUtils.isBlank(dataId)) { + where += " and data_id like ? "; + params.add(generateLikeArgument(dataId)); + } + if (!StringUtils.isBlank(group)) { + where += " and group_id like ? "; + params.add(generateLikeArgument(group)); + } + + if (!StringUtils.isBlank(tenantTmp)) { + where += " and tenant_id = ? "; + params.add(tenantTmp); + } + + if (!StringUtils.isBlank(appName)) { + where += " and app_name = ? "; + params.add(appName); + } + if (startTime != null) { + where += " and gmt_modified >=? "; + params.add(startTime); + } + if (endTime != null) { + where += " and gmt_modified <=? "; + params.add(endTime); + } + + PaginationHelper helper = new PaginationHelper(); + try { + return helper.fetchPage(jt, sqlCountRows + where, sqlFetchRows + where, params.toArray(), pageNo, pageSize, + lastMaxId, CONFIG_INFO_WRAPPER_ROW_MAPPER); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + public List findDeletedConfig(final Timestamp startTime, + final Timestamp endTime) { + try { + List> list = jt + .queryForList( + "SELECT distinct data_id, group_id, tenant_id FROM his_config_info where op_type = 'D' and gmt_modified >=? and gmt_modified <= ?", + new Object[] { startTime, endTime }); + return convertDeletedConfig(list); + } catch (DataAccessException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 增加配置;数据库原子操作,最小sql动作,无业务封装 + * @param srcIp ip + * @param srcUser user + * @param configInfo info + * @param time time + * @param configAdvanceInfo advance info + * @return excute sql result + */ + private long addConfigInfoAtomic(final String srcIp, final String srcUser, final ConfigInfo configInfo, final Timestamp time, + Map configAdvanceInfo) { + final String appNameTmp = StringUtils.isBlank(configInfo.getAppName()) ? StringUtils.EMPTY : configInfo.getAppName(); + final String tenantTmp = StringUtils.isBlank(configInfo.getTenant()) ? StringUtils.EMPTY : configInfo.getTenant(); + + final String desc = configAdvanceInfo == null ? null : (String)configAdvanceInfo.get("desc"); + final String use = configAdvanceInfo == null ? null : (String)configAdvanceInfo.get("use"); + final String effect = configAdvanceInfo == null ? null : (String)configAdvanceInfo.get("effect"); + final String type = configAdvanceInfo == null ? null : (String)configAdvanceInfo.get("type"); + final String schema = configAdvanceInfo == null ? null : (String)configAdvanceInfo.get("schema"); + + final String md5Tmp = MD5.getInstance().getMD5String(configInfo.getContent()); + + KeyHolder keyHolder = new GeneratedKeyHolder(); + + final String sql = "insert into config_info(data_id,group_id,tenant_id,app_name,content,md5,src_ip,src_user,gmt_create,gmt_modified,c_desc,c_use,effect,type,c_schema) values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; + + try { + jt.update(new PreparedStatementCreator() { + @SuppressFBWarnings(value = { "OBL_UNSATISFIED_OBLIGATION_EXCEPTION_EDGE", + "SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING" }, justification = "findbugs does not trust jdbctemplate, sql is constant in practice") + public PreparedStatement createPreparedStatement(Connection connection) throws SQLException { + PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); + ps.setString(1, configInfo.getDataId()); + ps.setString(2, configInfo.getGroup()); + ps.setString(3, tenantTmp); + ps.setString(4, appNameTmp); + ps.setString(5, configInfo.getContent()); + ps.setString(6, md5Tmp); + ps.setString(7, srcIp); + ps.setString(8, srcUser); + ps.setTimestamp(9, time); + ps.setTimestamp(10, time); + ps.setString(11, desc); + ps.setString(12, use); + ps.setString(13, effect); + ps.setString(14, type); + ps.setString(15, schema); + return ps; + } + }, keyHolder); + Number nu = keyHolder.getKey(); + if (nu == null) { + throw new IllegalArgumentException("insert config_info fail"); + } + return nu.longValue(); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + + /** + * 增加配置;数据库原子操作,最小sql动作,无业务封装 + * @param configId id + * @param tagName tag + * @param dataId data id + * @param group group + * @param tenant tenant + */ + public void addConfiTagRelationAtomic(long configId, String tagName, String dataId, String group, String tenant) { + try { + jt.update( + "insert into config_tags_relation(id,tag_name,tag_type,data_id,group_id,tenant_id) values(?,?,?,?,?,?)", + configId, tagName, null, dataId, group, tenant); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 增加配置;数据库原子操作,最小sql动作,无业务封装 + * @param configId config id + * @param configTags tags + * @param dataId dataId + * @param group group + * @param tenant tenant + */ + public void addConfiTagsRelationAtomic(long configId, String configTags, String dataId, String group, String tenant) { + if (StringUtils.isNotBlank(configTags)) { + String [] tagArr = configTags.split(","); + for (String tag : tagArr) { + addConfiTagRelationAtomic(configId, tag, dataId, group, tenant); + } + } + } + + public void removeTagByIdAtomic(long id) { + try { + jt.update("delete from config_tags_relation where id=?", id); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + public List getConfigTagsByTenant(String tenant) { + String sql = "select tag_name from config_tags_relation where tenant_id = ? "; + try { + return jt.queryForList(sql, new Object[] { tenant }, String.class); + } catch (EmptyResultDataAccessException e) { + return null; + } catch (IncorrectResultSizeDataAccessException e) { + return null; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + public List selectTagByConfig(String dataId, String group, String tenant) { + String sql = "select tag_name from config_tags_relation where data_id=? and group_id=? and tenant_id = ? "; + try { + return jt.queryForList(sql, new Object[] { dataId, group, tenant }, String.class); + } catch (EmptyResultDataAccessException e) { + return null; + } catch (IncorrectResultSizeDataAccessException e) { + return null; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 删除配置;数据库原子操作,最小sql动作,无业务封装 + * @param dataId dataId + * @param group group + * @param tenant tenant + * @param srcIp ip + * @param srcUser user + */ + private void removeConfigInfoAtomic(final String dataId, final String group, final String tenant, final String srcIp, + final String srcUser) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + try { + jt.update("delete from config_info where data_id=? and group_id=? and tenant_id=?", dataId, group, + tenantTmp); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 删除配置;数据库原子操作,最小sql动作,无业务封装 + * @param dataId dataId + * @param group group + * @param tenant tenant + * @param tag tag + * @param srcIp ip + * @param srcUser user + */ + public void removeConfigInfoTag(final String dataId, final String group, final String tenant, final String tag, final String srcIp, + final String srcUser) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + String tagTmp = StringUtils.isBlank(tag) ? StringUtils.EMPTY : tag; + try { + jt.update("delete from config_info_tag where data_id=? and group_id=? and tenant_id=? and tag_id=?", dataId, group, + tenantTmp,tagTmp); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 更新配置;数据库原子操作,最小sql动作,无业务封装 + * @param configInfo config info + * @param srcIp ip + * @param srcUser user + * @param time time + * @param configAdvanceInfo advance info + */ + private void updateConfigInfoAtomic(final ConfigInfo configInfo, final String srcIp, final String srcUser, + final Timestamp time, Map configAdvanceInfo) { + String appNameTmp = StringUtils.isBlank(configInfo.getAppName()) ? StringUtils.EMPTY : configInfo.getAppName(); + String tenantTmp = StringUtils.isBlank(configInfo.getTenant()) ? StringUtils.EMPTY : configInfo.getTenant(); + final String md5Tmp = MD5.getInstance().getMD5String(configInfo.getContent()); + String desc = configAdvanceInfo == null ? null : (String)configAdvanceInfo.get("desc"); + String use = configAdvanceInfo == null ? null : (String)configAdvanceInfo.get("use"); + String effect = configAdvanceInfo == null ? null : (String)configAdvanceInfo.get("effect"); + String type = configAdvanceInfo == null ? null : (String)configAdvanceInfo.get("type"); + String schema = configAdvanceInfo == null ? null : (String)configAdvanceInfo.get("schema"); + + try { + jt.update( + "update config_info set content=?, md5 = ?, src_ip=?,src_user=?,gmt_modified=?,app_name=?,c_desc=?,c_use=?,effect=?,type=?,c_schema=? where data_id=? and group_id=? and tenant_id=?", + configInfo.getContent(), md5Tmp, srcIp, srcUser, time, appNameTmp, desc, use, effect, type, schema, + configInfo.getDataId(), configInfo.getGroup(), tenantTmp); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 查询配置信息;数据库原子操作,最小sql动作,无业务封装 + * @param dataId dataId + * @param group group + * @param tenant tenant + * @return config info + */ + public ConfigInfo findConfigInfo(final String dataId, final String group, final String tenant) { + final String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + try { + return this.jt.queryForObject( + "select ID,data_id,group_id,tenant_id,app_name,content,md5 from config_info where data_id=? and group_id=? and tenant_id=?", + new Object[] { dataId, group, tenantTmp }, CONFIG_INFO_ROW_MAPPER); + } catch (EmptyResultDataAccessException e) { // 表明数据不存在, 返回null + return null; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 查询配置信息;数据库原子操作,最小sql动作,无业务封装 + * @param dataId dataId + * @param group group + * @param tenant tenant + * @return advance info + */ + public ConfigAdvanceInfo findConfigAdvanceInfo(final String dataId, final String group, final String tenant) { + final String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + try { + List configTagList = this.selectTagByConfig(dataId, group, tenant); + ConfigAdvanceInfo configAdvance = this.jt.queryForObject( + "select gmt_create,gmt_modified,src_user,src_ip,c_desc,c_use,effect,type,c_schema from config_info where data_id=? and group_id=? and tenant_id=?", + new Object[] { dataId, group, tenantTmp }, CONFIG_ADVANCE_INFO_ROW_MAPPER); + if (configTagList != null && !configTagList.isEmpty()) { + StringBuilder configTagsTmp = new StringBuilder(); + for (String configTag : configTagList) { + if (configTagsTmp.length() == 0) { + configTagsTmp.append(configTag); + } else { + configTagsTmp.append(",").append(configTag); + } + } + configAdvance.setConfigTags(configTagsTmp.toString()); + } + return configAdvance; + } catch (EmptyResultDataAccessException e) { // 表明数据不存在, 返回null + return null; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 更新变更记录;数据库原子操作,最小sql动作,无业务封装 + * @param id id + * @param configInfo config info + * @param srcIp ip + * @param srcUser user + * @param time time + * @param ops ops type + */ + private void insertConfigHistoryAtomic(long id, ConfigInfo configInfo, String srcIp, String srcUser, + final Timestamp time, String ops) { + String appNameTmp = StringUtils.isBlank(configInfo.getAppName()) ? StringUtils.EMPTY : configInfo.getAppName(); + String tenantTmp = StringUtils.isBlank(configInfo.getTenant()) ? StringUtils.EMPTY : configInfo.getTenant(); + final String md5Tmp = MD5.getInstance().getMD5String(configInfo.getContent()); + try { + jt.update( + "insert into his_config_info (id,data_id,group_id,tenant_id,app_name,content,md5,src_ip,src_user,gmt_modified,op_type) values(?,?,?,?,?,?,?,?,?,?,?)", + id, configInfo.getDataId(), configInfo.getGroup(), tenantTmp, appNameTmp, configInfo.getContent(), + md5Tmp, srcIp, srcUser, time, ops); + } catch (DataAccessException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * list配置的历史变更记录 + * + * @param dataId + * data Id + * @param group + * group + * @param tenant + * tenant + * @param pageNo + * no + * @param pageSize + * size + * @return history info + */ + public Page findConfigHistory(String dataId, String group, String tenant, int pageNo, int pageSize) { + PaginationHelper helper = new PaginationHelper(); + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + String sqlCountRows = "select count(*) from his_config_info where data_id = ? and group_id = ? and tenant_id = ? order by nid desc"; + String sqlFetchRows = "select nid,data_id,group_id,tenant_id,app_name,src_ip,op_type,gmt_create,gmt_modified from his_config_info where data_id = ? and group_id = ? and tenant_id = ? order by nid desc"; + + Page page = null; + try { + page = helper.fetchPage(this.jt, sqlCountRows, sqlFetchRows, new Object[] { dataId, group, tenantTmp }, pageNo, + pageSize, HISTORY_LIST_ROW_MAPPER); + } catch (DataAccessException e) { + fatalLog.error("[list-config-history] error, dataId:{}, group:{}", new Object[] { dataId, group }, e); + throw e; + } + return page; + } + + /** + * 增加配置;数据库原子操作,最小sql动作,无业务封装 + * @param dataId dataId + * @param group group + * @param appName appName + * @param date date + */ + private void addConfigSubAtomic(final String dataId, final String group, final String appName, + final Timestamp date) { + final String appNameTmp = appName == null ? "" : appName; + try { + jt.update( + "insert into app_configdata_relation_subs(data_id,group_id,app_name,gmt_modified) values(?,?,?,?)", + dataId, group, appNameTmp, date); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + /** + * 更新配置;数据库原子操作,最小sql动作,无业务封装 + * + * @param dataId + * data Id + * @param group + * group + * @param appName + * app name + * @param time + * time + */ + private void updateConfigSubAtomic(final String dataId, final String group, final String appName, + final Timestamp time) { + final String appNameTmp = appName == null ? "" : appName; + try { + jt.update( + "update app_configdata_relation_subs set gmt_modified=? where data_id=? and group_id=? and app_name=?", + time, dataId, group, appNameTmp); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + public ConfigHistoryInfo detailConfigHistory(Long nid) { + String sqlFetchRows = "select nid,data_id,group_id,tenant_id,app_name,content,md5,src_user,src_ip,op_type,gmt_create,gmt_modified from his_config_info where nid = ?"; + try { + ConfigHistoryInfo historyInfo = jt.queryForObject(sqlFetchRows, new Object[] { nid }, + HISTORY_DETAIL_ROW_MAPPER); + return historyInfo; + } catch (DataAccessException e) { + fatalLog.error("[list-config-history] error, nid:{}", new Object[] { nid }, e); + throw e; + } + } + + private List convertDeletedConfig(List> list) { + List configs = new ArrayList(); + for (Map map : list) { + String dataId = (String) map.get("data_id"); + String group = (String) map.get("group_id"); + String tenant = (String) map.get("tenant_id"); + ConfigInfo config = new ConfigInfo(); + config.setDataId(dataId); + config.setGroup(group); + config.setTenant(tenant); + configs.add(config); + } + return configs; + } + + private List convertChangeConfig( + List> list) { + List configs = new ArrayList(); + for (Map map : list) { + String dataId = (String) map.get("data_id"); + String group = (String) map.get("group_id"); + String tenant = (String) map.get("tenant_id"); + String content = (String) map.get("content"); + long mTime = ((Timestamp)map.get("gmt_modified")).getTime(); + ConfigInfoWrapper config = new ConfigInfoWrapper(); + config.setDataId(dataId); + config.setGroup(group); + config.setTenant(tenant); + config.setContent(content); + config.setLastModified(mTime); + configs.add(config); + } + return configs; + } + + /** + * 获取所有的配置的Md5值,通过分页方式获取。 + * + * @return + */ + public List listAllGroupKeyMd5() { + final int pageSize = 10000; + int totalCount = configInfoCount(); + int pageCount = (int) Math.ceil(totalCount * 1.0 / pageSize); + List allConfigInfo = new ArrayList(); + for (int pageNo = 1; pageNo <= pageCount; pageNo++) { + List configInfoList = listGroupKeyMd5ByPage(pageNo, pageSize); + allConfigInfo.addAll(configInfoList); + } + return allConfigInfo; + } + + private List listGroupKeyMd5ByPage(int pageNo, int pageSize) { + String sqlCountRows = " SELECT COUNT(*) FROM config_info "; + String sqlFetchRows = " SELECT t.id,data_id,group_id,tenant_id,app_name,md5,gmt_modified FROM ( SELECT id FROM config_info ORDER BY id LIMIT ?,? ) g, config_info t WHERE g.id = t.id"; + PaginationHelper helper = new PaginationHelper(); + try { + Page page = helper.fetchPageLimit(jt, sqlCountRows, sqlFetchRows, new Object[] { + (pageNo - 1) * pageSize, pageSize }, pageNo, pageSize, CONFIG_INFO_WRAPPER_ROW_MAPPER); + + return page.getPageItems(); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + + private String generateLikeArgument(String s) { + if (s.indexOf(PATTERN_STR) >= 0) + return s.replaceAll("\\*", "%"); + else { + return s; + } + } + + public ConfigInfoWrapper queryConfigInfo(final String dataId, final String group, final String tenant) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + try { + return this.jt + .queryForObject( + "select ID,data_id,group_id,tenant_id,app_name,content,gmt_modified,md5 from config_info where data_id=? and group_id=? and tenant_id=?", + new Object[] { dataId, group, tenantTmp }, CONFIG_INFO_WRAPPER_ROW_MAPPER); + } catch (EmptyResultDataAccessException e) { + return null; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error] " + e.toString(), e); + throw e; + } + } + + public boolean isExistTable(String tableName) + { + String sql = "SELECT COUNT(*) FROM " +tableName; + try { + jt.queryForObject(sql, Integer.class); + return true; + } catch (Throwable e) { + return false; + } + } + + public Boolean completeMd5() { + defaultLog.info("[start completeMd5]"); + int perPageSize = 1000; + int rowCount = configInfoCount(); + int pageCount = (int) Math.ceil(rowCount * 1.0 / perPageSize); + int actualRowCount = 0; + for (int pageNo = 1; pageNo <= pageCount; pageNo++) { + Page page = findAllConfigInfoForDumpAll( + pageNo, perPageSize); + if (page != null) { + for (PersistService.ConfigInfoWrapper cf : page.getPageItems()) { + String md5InDb = cf.getMd5(); + final String content = cf.getContent(); + final String tenant = cf.getTenant(); + final String md5 = MD5.getInstance().getMD5String( + content); + if (StringUtils.isBlank(md5InDb)) { + try { + updateMd5(cf.getDataId(), cf.getGroup(), tenant, md5, new Timestamp(cf.getLastModified())); + } catch (Exception e) { + LogUtil.defaultLog + .error("[completeMd5-error] datId:{} group:{} lastModified:{}", + new Object[] { + cf.getDataId(), + cf.getGroup(), + new Timestamp(cf + .getLastModified()) }); + } + } else + { + if (!md5InDb.equals(md5)) { + try { + updateMd5(cf.getDataId(), cf.getGroup(), tenant, md5, new Timestamp(cf.getLastModified())); + } catch (Exception e) { + LogUtil.defaultLog.error("[completeMd5-error] datId:{} group:{} lastModified:{}", + new Object[] { cf.getDataId(), cf.getGroup(), + new Timestamp(cf.getLastModified()) }); + } + } + } + } + + actualRowCount += page.getPageItems().size(); + defaultLog.info("[completeMd5] {} / {}", actualRowCount, rowCount); + } + } + return true; + } + + static final ConfigInfoWrapperRowMapper CONFIG_INFO_WRAPPER_ROW_MAPPER = new ConfigInfoWrapperRowMapper(); + + static final ConfigKeyRowMapper CONFIG_KEY_ROW_MAPPER = new ConfigKeyRowMapper(); + + static final ConfigInfoBetaWrapperRowMapper CONFIG_INFO_BETA_WRAPPER_ROW_MAPPER = new ConfigInfoBetaWrapperRowMapper(); + + static final ConfigInfoTagWrapperRowMapper CONFIG_INFO_TAG_WRAPPER_ROW_MAPPER = new ConfigInfoTagWrapperRowMapper(); + + static final ConfigInfoRowMapper CONFIG_INFO_ROW_MAPPER = new ConfigInfoRowMapper(); + + static final ConfigAdvanceInfoRowMapper CONFIG_ADVANCE_INFO_ROW_MAPPER = new ConfigAdvanceInfoRowMapper(); + + static final ConfigInfo4BetaRowMapper CONFIG_INFO4BETA_ROW_MAPPER = new ConfigInfo4BetaRowMapper(); + + static final ConfigInfo4TagRowMapper CONFIG_INFO4TAG_ROW_MAPPER = new ConfigInfo4TagRowMapper(); + + static final ConfigInfoBaseRowMapper CONFIG_INFO_BASE_ROW_MAPPER = new ConfigInfoBaseRowMapper(); + + static final ConfigInfoAggrRowMapper CONFIG_INFO_AGGR_ROW_MAPPER = new ConfigInfoAggrRowMapper(); + + static final ConfigInfoChangedRowMapper CONFIG_INFO_CHANGED_ROW_MAPPER = new ConfigInfoChangedRowMapper(); + + static final ConfigHistoryRowMapper HISTORY_LIST_ROW_MAPPER = new ConfigHistoryRowMapper(); + + static final ConfigHistoryDetailRowMapper HISTORY_DETAIL_ROW_MAPPER = new ConfigHistoryDetailRowMapper(); + + private static String PATTERN_STR = "*"; + private final static int QUERY_LIMIT_SIZE = 50; + private JdbcTemplate jt; + private TransactionTemplate tjt; + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/ServerListService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/ServerListService.java new file mode 100644 index 00000000000..1b6d40bc068 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/ServerListService.java @@ -0,0 +1,477 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service; + +import static com.alibaba.nacos.config.server.utils.LogUtil.defaultLog; +import static com.alibaba.nacos.config.server.utils.LogUtil.fatalLog; + +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +import javax.annotation.PostConstruct; +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.http.HttpResponse; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.utils.HttpClientUtils; +import org.apache.http.concurrent.FutureCallback; +import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; +import org.apache.http.impl.nio.client.HttpAsyncClients; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.context.WebServerInitializedEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Service; + +import com.alibaba.nacos.config.server.service.notify.NotifyService; +import com.alibaba.nacos.config.server.service.notify.NotifyService.HttpResult; +import com.alibaba.nacos.config.server.utils.LogUtil; +import com.alibaba.nacos.config.server.utils.PropertyUtil; +import com.alibaba.nacos.config.server.utils.RunningConfigUtils; +import com.alibaba.nacos.config.server.utils.SystemConfig; +import com.alibaba.nacos.config.server.utils.event.EventDispatcher; + +/** + * Serverlist service + * @author Nacos + * + */ +@Service +public class ServerListService implements ApplicationListener { + + @Autowired + private Environment env; + + @Autowired + private ServletContext servletContext; + + @Value("${server.port}") + private int port; + + + @PostConstruct + public void init() { + serverPort = System.getProperty("nacos.server.port", "8080"); + String envDomainName = System.getenv("address_server_domain"); + if (StringUtils.isBlank(envDomainName)) { + domainName = System.getProperty("address.server.domain", "jmenv.tbsite.net"); + } else { + domainName = envDomainName; + } + String envAddressPort = System.getenv("address_server_port"); + if (StringUtils.isBlank(envAddressPort)) { + addressPort = System.getProperty("address.server.port", "8080"); + } else { + addressPort = envAddressPort; + } + addressUrl = System.getProperty("address.server.url", + servletContext.getContextPath() + "/" + RunningConfigUtils.getClusterName()); + addressServerUrl = "http://" + domainName + ":" + addressPort + addressUrl; + envIdUrl = "http://" + domainName + ":" + addressPort + "/env"; + + defaultLog.info("ServerListService address-server port:" + serverPort); + defaultLog.info("ADDRESS_SERVER_URL:" + addressServerUrl); + isHealthCheck = PropertyUtil.isHealthCheck(); + maxFailCount = PropertyUtil.getMaxHealthCheckFailCount(); + + try { + String val = null; + val = env.getProperty("useAddressServer"); + if (val != null && FALSE_STR.equals(val)) { + isUseAddressServer = false; + } + fatalLog.warn("useAddressServer:{}", isUseAddressServer); + } catch (Exception e) { + fatalLog.error("read application.properties wrong", e); + } + GetServerListTask task = new GetServerListTask(); + task.run(); + if (null == serverList || serverList.isEmpty()) { + fatalLog.error("########## cannot get serverlist, so exit."); + throw new RuntimeException("cannot get serverlist, so exit."); + } else { + TimerTaskService.scheduleWithFixedDelay(task, 0L, 5L, TimeUnit.SECONDS); + } + httpclient.start(); + CheckServerHealthTask checkServerHealthTask = new CheckServerHealthTask(); + TimerTaskService.scheduleWithFixedDelay(checkServerHealthTask, 0L, 5L, TimeUnit.SECONDS); + } + + public String getEnvId() { + String envId = ""; + int i = 0; + do { + envId = getEnvIdHttp(); + if (StringUtils.isBlank(envId)) { + i++; + try { + Thread.sleep(500); + } catch (InterruptedException e) { + LogUtil.defaultLog.error("sleep interrupt"); + } + } + } while (StringUtils.isBlank(envId) && i < 5); + + if (!StringUtils.isBlank(envId)) { + } else { + LogUtil.defaultLog.error("envId is blank"); + } + return envId; + } + + public List getServerList() { + return new ArrayList(serverList); + } + + public static void setServerList(List serverList) { + ServerListService.serverList = serverList; + } + + public static List getServerListUnhealth() { + return new ArrayList(serverListUnhealth); + } + + public static Boolean isFirstIp() { + return serverList.get(0).contains(SystemConfig.LOCAL_IP); + } + + public boolean isHealthCheck() { + return isHealthCheck; + } + + /** serverList has changed */ + static public class ServerlistChangeEvent implements EventDispatcher.Event {} + private void updateIfChanged(List newList) { + if (newList.isEmpty()) { + return; + } + + boolean isContainSelfIp = false; + for (String ipPortTmp : newList) { + if (ipPortTmp.contains(SystemConfig.LOCAL_IP)) { + isContainSelfIp = true; + break; + } + } + + if (isContainSelfIp) { + isInIpList = true; + } else { + isInIpList = false; + String selfAddr = getFormatServerAddr(SystemConfig.LOCAL_IP); + newList.add(selfAddr); + fatalLog.error("########## [serverlist] self ip {} not in serverlist {}", selfAddr, newList); + } + + if (newList.equals(serverList)) { + return; + } + + serverList = new ArrayList(newList); + + List unhealthRemoved = new ArrayList(); + for (String unhealthIp : serverListUnhealth) { + if (!newList.contains(unhealthIp)) { + unhealthRemoved.add(unhealthIp); + } + } + + serverListUnhealth.removeAll(unhealthRemoved); + + List unhealthCountRemoved = new ArrayList(); + for (Map.Entry ip2UnhealthCountTmp : serverIp2unhealthCount.entrySet()) { + if (!newList.contains(ip2UnhealthCountTmp.getKey())) { + unhealthCountRemoved.add(ip2UnhealthCountTmp.getKey()); + } + } + + for (String unhealthCountTmp : unhealthCountRemoved) { + serverIp2unhealthCount.remove(unhealthCountTmp); + } + + defaultLog.warn("[serverlist] updated to {}", serverList); + + /** + * 非并发fireEvent + */ + EventDispatcher.fireEvent(new ServerlistChangeEvent()); + } + + /** + * 保证不返回NULL + * + * @return serverlist + */ + private List getApacheServerList() { + // 优先从文件读取服务列表 + try { + List serverIps = new ArrayList(); + String serverIpsStr = DiskUtil.getServerList(); + if (!StringUtils.isBlank(serverIpsStr)) { + String split = System.getProperty("line.separator"); + String[] serverAddrArr = serverIpsStr.split(split); + for (String serverAddr : serverAddrArr) { + if (StringUtils.isNotBlank(serverAddr.trim())) { + serverIps.add(getFormatServerAddr(serverAddr)); + } + } + } + if (serverIps.size() > 0) { + return serverIps; + } + } catch (Exception e) { + defaultLog.error("nacos-XXXX", "[serverlist] failed to get serverlist from disk!", e); + } + + if (isUseAddressServer() && !PropertyUtil.isStandaloneMode()) { + try { + HttpResult result = NotifyService.invokeURL(addressServerUrl, null, null); + + if (HttpServletResponse.SC_OK == result.code) { + isAddressServerHealth = true; + addressServerFailCcount = 0; + List lines = IOUtils.readLines(new StringReader(result.content)); + List ips = new ArrayList(lines.size()); + for (String serverAddr : lines) { + if (null == serverAddr || serverAddr.trim().isEmpty()) { + continue; + } else { + ips.add(getFormatServerAddr(serverAddr)); + } + } + return ips; + } else { + addressServerFailCcount++; + if (addressServerFailCcount >= maxFailCount) { + isAddressServerHealth = false; + } + defaultLog.error("[serverlist] failed to get serverlist, error code {}", result.code); + return Collections.emptyList(); + } + } catch (IOException e) { + addressServerFailCcount++; + if (addressServerFailCcount >= maxFailCount) { + isAddressServerHealth = false; + } + defaultLog.error("[serverlist] exception, " + e.toString(), e); + return Collections.emptyList(); + } + + } else { + List serverIps = new ArrayList(); + serverIps.add(getFormatServerAddr(SystemConfig.LOCAL_IP)); + return serverIps; + } + } + + private String getFormatServerAddr(String serverAddr) { + if (StringUtils.isBlank(serverAddr)) { + throw new IllegalArgumentException("invalid serverlist"); + } + String[] ipPort = serverAddr.trim().split(":"); + String ip = ipPort[0].trim(); + if (ipPort.length == 1 && port != 0) { + return (ip + ":" + port); + } else { + return serverAddr; + } + } + + private String getEnvIdHttp() { + try { + // "http://jmenv.tbsite.net:8080/env"; + HttpResult result = NotifyService.invokeURL(envIdUrl, null, null); + + if (HttpServletResponse.SC_OK == result.code) { + return result.content.trim(); + } else { + defaultLog.error("[envId] failed to get envId, error code {}", result.code); + return ""; + } + } catch (IOException e) { + defaultLog.error("[envId] exception, " + e.toString(), e); + return ""; + } + + } + + class GetServerListTask implements Runnable { + @Override + public void run() { + try { + updateIfChanged(getApacheServerList()); + } catch (Exception e) { + defaultLog.error("[serverlist] failed to get serverlist, " + e.toString(), e); + } + } + } + + private void checkServerHealth() { + long startCheckTime = System.currentTimeMillis(); + for (String serverIp : serverList) { + // Compatible with old codes,use status.taobao + String url = "http://" + serverIp + servletContext.getContextPath() + "/health"; + // "/nacos/health"; + HttpGet request = new HttpGet(url); + httpclient.execute(request, new AyscCheckServerHealthCallBack(serverIp)); + } + long endCheckTime = System.currentTimeMillis(); + long cost = endCheckTime - startCheckTime; + defaultLog.debug("checkServerHealth cost: {}", cost); + } + + class AyscCheckServerHealthCallBack implements FutureCallback { + + private String serverIp; + + public AyscCheckServerHealthCallBack(String serverIp) { + this.serverIp = serverIp; + } + + @Override + public void completed(HttpResponse response) { + if (response.getStatusLine().getStatusCode() == HttpServletResponse.SC_OK) { + serverIp2unhealthCount.put(serverIp, 0); + if (serverListUnhealth.contains(serverIp)) { + serverListUnhealth.remove(serverIp); + } + HttpClientUtils.closeQuietly(response); + } + } + + @Override + public void failed(Exception ex) { + Integer failCount = serverIp2unhealthCount.get(serverIp); + failCount = failCount == null ? Integer.valueOf(0) : failCount; + failCount++; + serverIp2unhealthCount.put(serverIp, failCount); + if (failCount > maxFailCount) { + if (!serverListUnhealth.contains(serverIp)) { + serverListUnhealth.add(serverIp); + } + defaultLog.error("unhealthIp:{}, unhealthCount:{}", serverIp, failCount); + } + } + + @Override + public void cancelled() { + Integer failCount = serverIp2unhealthCount.get(serverIp); + failCount = failCount == null ? Integer.valueOf(0) : failCount; + failCount++; + serverIp2unhealthCount.put(serverIp, failCount); + if (failCount > maxFailCount) { + if (!serverListUnhealth.contains(serverIp)) { + serverListUnhealth.add(serverIp); + } + defaultLog.error("unhealthIp:{}, unhealthCount:{}", serverIp, failCount); + } + } + } + + class CheckServerHealthTask implements Runnable{ + + @Override + public void run() { + checkServerHealth(); + } + + } + + private Boolean isUseAddressServer() { + return isUseAddressServer; + } + + static class CheckServerThreadFactory implements ThreadFactory { + + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r, "com.alibaba.nacos.CheckServerThreadFactory"); + thread.setDaemon(true); + return thread; + } + } + + public static boolean isAddressServerHealth() { + return isAddressServerHealth; + } + + public static boolean isInIpList() { + return isInIpList; + } + + // ========================== + + /** + * 和其他server的连接超时和socket超时 + */ + static final int TIMEOUT = 5000; + private int maxFailCount = 12; + private static volatile List serverList = new ArrayList(); + private static volatile List serverListUnhealth = new ArrayList(); + private static volatile boolean isAddressServerHealth = true; + private static volatile int addressServerFailCcount = 0; + private static volatile boolean isInIpList = true; + + /** + * ip unhealth count + */ + private static volatile Map serverIp2unhealthCount = new HashMap(); + private RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(PropertyUtil.getNotifyConnectTimeout()) + .setSocketTimeout(PropertyUtil.getNotifySocketTimeout()).build(); + + private CloseableHttpAsyncClient httpclient = HttpAsyncClients.custom().setDefaultRequestConfig(requestConfig) + .build(); + + /** + * server之间通信的端口 + */ + public String serverPort; + public String domainName; + public String addressPort; + public String addressUrl; + public String envIdUrl; + public String addressServerUrl; + private Boolean isUseAddressServer = true; + private boolean isHealthCheck = true; + private final static String FALSE_STR = "false"; + + + @Override + public void onApplicationEvent(WebServerInitializedEvent event) { + if (port == 0) { + port = event.getWebServer().getPort(); + List newList = new ArrayList(); + for (String serverAddrTmp : serverList) { + newList.add(getFormatServerAddr(serverAddrTmp)); + } + setServerList(new ArrayList(newList)); + } + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/SwitchService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/SwitchService.java new file mode 100755 index 00000000000..0e90710201d --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/SwitchService.java @@ -0,0 +1,126 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; +import org.springframework.stereotype.Service; + +import com.alibaba.nacos.config.server.utils.LogUtil; + +import static com.alibaba.nacos.config.server.utils.LogUtil.fatalLog; + +import java.io.IOException; +import java.io.StringReader; +import java.util.HashMap; +import java.util.Map; + +/** + * Switch + * @author Nacos + */ +@Service +public class SwitchService { + public static final String SWITCH_META_DATAID = "com.alibaba.nacos.meta.switch"; + + public static final String FIXED_POLLING = "isFixedPolling"; + public static final String FIXED_POLLING_INTERVAL = "fixedPollingInertval"; + + public static final String FIXED_DELAY_TIME = "fixedDelayTime"; + + public static final String DISABLE_APP_COLLECTOR = "disableAppCollector"; + + private static volatile Map switches = new HashMap(); + + public static boolean getSwitchBoolean(String key, boolean defaultValue) { + boolean rtn = defaultValue; + try { + String value = switches.get(key); + rtn = value != null ? Boolean.valueOf(value).booleanValue() : defaultValue; + } catch (Exception e) { + rtn = defaultValue; + LogUtil.fatalLog.error("corrupt switch value {}={}", new Object[]{key, switches.get(key)}); + } + return rtn; + } + + public static int getSwitchInteger(String key, int defaultValue) { + int rtn = defaultValue; + try { + String status = switches.get(key); + rtn = status != null ? Integer.parseInt(status) : defaultValue; + } catch (Exception e) { + rtn = defaultValue; + LogUtil.fatalLog.error("corrupt switch value {}={}", new Object[]{key, switches.get(key)}); + } + return rtn; + } + + + public static String getSwitchString(String key, String defaultValue){ + String value = switches.get(key); + return StringUtils.isBlank(value) ? defaultValue : value ; + } + + public static void load(String config) { + if (StringUtils.isBlank(config)) { + fatalLog.error("switch config is blank."); + return; + } + fatalLog.warn("[switch-config] {}", config); + + Map map = new HashMap(30); + try { + for (String line : IOUtils.readLines(new StringReader(config))) { + if (!StringUtils.isBlank(line) && !line.startsWith("#")) { + String[] array = line.split("="); + + if (array == null || array.length != 2) { + LogUtil.fatalLog.error("corrupt switch record {}", line); + continue; + } + + String key = array[0].trim(); + String value = array[1].trim(); + + map.put(key, value); + } + switches = map; + fatalLog.warn("[reload-switches] {}", getSwitches()); + } + } catch (IOException e) { + LogUtil.fatalLog.warn("[reload-switches] error! {}", config); + } + } + + public static String getSwitches() { + StringBuilder sb = new StringBuilder(); + + String split = ""; + for (Map.Entry entry : switches.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + sb.append(split); + sb.append(key); + sb.append("="); + sb.append(value); + split = "; "; + } + + return sb.toString(); + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/TimerTaskService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/TimerTaskService.java new file mode 100644 index 00000000000..11b929f31e8 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/TimerTaskService.java @@ -0,0 +1,49 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + + +/** + * 定时任务服务 + * @author Nacos + */ +public class TimerTaskService { + @SuppressWarnings("PMD.ThreadPoolCreationRule") + private static ScheduledExecutorService scheduledExecutorService = Executors + .newScheduledThreadPool(10, new ThreadFactory() { + AtomicInteger count = new AtomicInteger(0); + + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setDaemon(true); + t.setName("com.alibaba.nacos.server.Timer-" + count.getAndIncrement()); + return t; + } + }); + + static public void scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, + TimeUnit unit) { + scheduledExecutorService.scheduleWithFixedDelay(command, initialDelay, delay, unit); + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/capacity/CapacityService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/capacity/CapacityService.java new file mode 100644 index 00000000000..a5b8a213e84 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/capacity/CapacityService.java @@ -0,0 +1,499 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service.capacity; + +import com.alibaba.nacos.config.server.constant.CounterMode; +import com.alibaba.nacos.config.server.model.capacity.Capacity; +import com.alibaba.nacos.config.server.model.capacity.GroupCapacity; +import com.alibaba.nacos.config.server.model.capacity.TenantCapacity; +import com.alibaba.nacos.config.server.service.PersistService; +import com.alibaba.nacos.config.server.utils.PropertyUtil; +import com.alibaba.nacos.config.server.utils.LogUtil; +import com.alibaba.nacos.config.server.utils.TimeUtils; +import com.google.common.base.Stopwatch; +import com.google.common.util.concurrent.ThreadFactoryBuilder; + +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.sql.Timestamp; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +/** + * Capacity service + * @author hexu.hxy + * @date 2018/03/05 + */ +@Service +public class CapacityService { + private static final Logger LOGGER = LoggerFactory.getLogger(CapacityService.class); + + private static final Integer ZERO = 0; + private static final int INIT_PAGE_SIZE = 500; + + @Autowired + private GroupCapacityPersistService groupCapacityPersistService; + @Autowired + private TenantCapacityPersistService tenantCapacityPersistService; + @Autowired + private PersistService persistService; + + private ScheduledExecutorService scheduledExecutorService; + + @PostConstruct + @SuppressWarnings("PMD.ThreadPoolCreationRule") + public void init() { + // 每个Server都有修正usage的Job在跑,幂等 + ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat( + "com.alibaba.nacos.CapacityManagement-%d").setDaemon(true).build(); + scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(threadFactory); + scheduledExecutorService.scheduleWithFixedDelay(new Runnable() { + @Override + public void run() { + LOGGER.info("[capacityManagement] start correct usage"); + Stopwatch stopwatch = Stopwatch.createStarted(); + correctUsage(); + LOGGER.info("[capacityManagement] end correct usage, cost: {}s", stopwatch.elapsed(TimeUnit.SECONDS)); + + } + }, PropertyUtil.getCorrectUsageDelay(), PropertyUtil.getCorrectUsageDelay(), TimeUnit.SECONDS); + } + + @PreDestroy + public void destroy() { + scheduledExecutorService.shutdown(); + } + + public void correctUsage() { + correctGroupUsage(); + correctTenantUsage(); + } + + public void correctGroupUsage(String group) { + groupCapacityPersistService.correctUsage(group, TimeUtils.getCurrentTime()); + } + + public void correctTenantUsage(String tenant) { + tenantCapacityPersistService.correctUsage(tenant, TimeUtils.getCurrentTime()); + } + + public void initAllCapacity() { + initAllCapacity(false); + initAllCapacity(true); + } + + private void initAllCapacity(boolean isTenant) { + int page = 1; + while (true) { + List list; + if (isTenant) { + list = persistService.getTenantIdList(page, INIT_PAGE_SIZE); + } else { + list = persistService.getGroupIdList(page, INIT_PAGE_SIZE); + } + for (String targetId : list) { + if (isTenant) { + insertTenantCapacity(targetId); + autoExpansion(null, targetId); + } else { + insertGroupCapacity(targetId); + autoExpansion(targetId, null); + } + } + if (list.size() < INIT_PAGE_SIZE) { + break; + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + // ignore + } + ++page; + } + } + + /** + * 修正Group容量信息中的使用值(usage) + */ + private void correctGroupUsage() { + long lastId = 0; + int pageSize = 100; + while (true) { + List groupCapacityList = groupCapacityPersistService.getCapacityList4CorrectUsage(lastId, + pageSize); + if (groupCapacityList.isEmpty()) { + break; + } + lastId = groupCapacityList.get(groupCapacityList.size() - 1).getId(); + for (GroupCapacity groupCapacity : groupCapacityList) { + String group = groupCapacity.getGroup(); + groupCapacityPersistService.correctUsage(group, TimeUtils.getCurrentTime()); + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + // ignore + } + } + } + + /** + * 修正Tenant容量信息中的使用值(usage) + */ + private void correctTenantUsage() { + long lastId = 0; + int pageSize = 100; + while (true) { + List tenantCapacityList = tenantCapacityPersistService.getCapacityList4CorrectUsage(lastId, + pageSize); + if (tenantCapacityList.isEmpty()) { + break; + } + lastId = tenantCapacityList.get(tenantCapacityList.size() - 1).getId(); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + // ignore + } + for (TenantCapacity tenantCapacity : tenantCapacityList) { + String tenant = tenantCapacity.getTenant(); + tenantCapacityPersistService.correctUsage(tenant, TimeUtils.getCurrentTime()); + } + } + } + + /** + * 集群:1. 如果容量信息不存在,则初始化容量信息
2. 更新容量的使用量usage,加一或减一 + * + * @param counterMode 增加或者减少 + * @param ignoreQuotaLimit 是否忽略容量额度限制,在关闭容量管理的限制检验功能只计数的时候为true,开启容量管理的限制检验功能则为false + * @return 是否操作成功 + */ + public boolean insertAndUpdateClusterUsage(CounterMode counterMode, boolean ignoreQuotaLimit) { + Capacity capacity = groupCapacityPersistService.getClusterCapacity(); + if (capacity == null) { + insertGroupCapacity(GroupCapacityPersistService.CLUSTER); + } + return updateGroupUsage(counterMode, GroupCapacityPersistService.CLUSTER, + PropertyUtil.getDefaultClusterQuota(), ignoreQuotaLimit); + } + + public boolean updateClusterUsage(CounterMode counterMode) { + return updateGroupUsage(counterMode, GroupCapacityPersistService.CLUSTER, + PropertyUtil.getDefaultClusterQuota(), false); + } + + /** + * 提供给关闭容量管理的限制检验功能时计数使用
Group:1. 如果容量信息不存在,则初始化容量信息
2. 更新容量的使用量usage,加一或减一 + * + * @param counterMode 增加或者减少 + * @param group group + * @param ignoreQuotaLimit 是否忽略容量额度限制,在关闭容量管理的限制检验功能只计数的时候为true,开启容量管理的限制检验功能则为false + * @return 是否操作成功 + */ + public boolean insertAndUpdateGroupUsage(CounterMode counterMode, String group, boolean ignoreQuotaLimit) { + GroupCapacity groupCapacity = getGroupCapacity(group); + if (groupCapacity == null) { + initGroupCapacity(group, null, null, null, null); + } + return updateGroupUsage(counterMode, group, PropertyUtil.getDefaultGroupQuota(), ignoreQuotaLimit); + } + + public GroupCapacity getGroupCapacity(String group) { + return groupCapacityPersistService.getGroupCapacity(group); + } + + public boolean updateGroupUsage(CounterMode counterMode, String group) { + return updateGroupUsage(counterMode, group, PropertyUtil.getDefaultGroupQuota(), false); + } + + /** + * 初始化该Group的容量信息,如果到达限额,将自动扩容,以降低运维成本 + */ + public boolean initGroupCapacity(String group) { + return initGroupCapacity(group, null, null, null, null); + } + + /** + * 初始化该Group的容量信息,如果到达限额,将自动扩容,以降低运维成本 + */ + private boolean initGroupCapacity(String group, Integer quota, Integer maxSize, Integer maxAggrCount, + Integer maxAggrSize) { + boolean insertSuccess = insertGroupCapacity(group, quota, maxSize, maxAggrCount, maxAggrSize); + if (quota != null) { + return insertSuccess; + } + autoExpansion(group, null); + return insertSuccess; + } + + /** + * 自动扩容 + */ + private void autoExpansion(String group, String tenant) { + Capacity capacity = getCapacity(group, tenant); + int defaultQuota = getDefaultQuota(tenant != null); + Integer usage = capacity.getUsage(); + if (usage < defaultQuota) { + return; + } + // 初始化的时候该Group/租户就已经到达限额,自动扩容,降低运维成本 + int initialExpansionPercent = PropertyUtil.getInitialExpansionPercent(); + if (initialExpansionPercent > 0) { + int finalQuota = (int)(usage + defaultQuota * (1.0 * initialExpansionPercent / 100)); + if (tenant != null) { + tenantCapacityPersistService.updateQuota(tenant, finalQuota); + LogUtil.defaultLog.warn("[capacityManagement] 初始化的时候该租户({})使用量({})就已经到达限额{},自动扩容到{}", tenant, + usage, defaultQuota, finalQuota); + } else { + groupCapacityPersistService.updateQuota(group, finalQuota); + LogUtil.defaultLog.warn("[capacityManagement] 初始化的时候该Group({})使用量({})就已经到达限额{},自动扩容到{}", group, + usage, defaultQuota, finalQuota); + } + } + } + + private int getDefaultQuota(boolean isTenant) { + if (isTenant) { + return PropertyUtil.getDefaultTenantQuota(); + } + return PropertyUtil.getDefaultGroupQuota(); + } + + public Capacity getCapacity(String group, String tenant) { + if (tenant != null) { + return getTenantCapacity(tenant); + } + return getGroupCapacity(group); + } + + public Capacity getCapacityWithDefault(String group, String tenant) { + Capacity capacity; + boolean isTenant = StringUtils.isNotBlank(tenant); + if (isTenant) { + capacity = getTenantCapacity(tenant); + } else { + capacity = getGroupCapacity(group); + } + if (capacity == null) { + return null; + } + Integer quota = capacity.getQuota(); + if (quota == 0) { + if (isTenant) { + capacity.setQuota(PropertyUtil.getDefaultTenantQuota()); + } else { + if (GroupCapacityPersistService.CLUSTER.equals(group)) { + capacity.setQuota(PropertyUtil.getDefaultClusterQuota()); + } else { + capacity.setQuota(PropertyUtil.getDefaultGroupQuota()); + } + } + } + Integer maxSize = capacity.getMaxSize(); + if (maxSize == 0) { + capacity.setMaxSize(PropertyUtil.getDefaultMaxSize()); + } + Integer maxAggrCount = capacity.getMaxAggrCount(); + if (maxAggrCount == 0) { + capacity.setMaxAggrCount(PropertyUtil.getDefaultMaxAggrCount()); + } + Integer maxAggrSize = capacity.getMaxAggrSize(); + if (maxAggrSize == 0) { + capacity.setMaxAggrSize(PropertyUtil.getDefaultMaxAggrSize()); + } + return capacity; + } + + public boolean initCapacity(String group, String tenant) { + if (StringUtils.isNotBlank(tenant)) { + return initTenantCapacity(tenant); + } + if (GroupCapacityPersistService.CLUSTER.equals(group)) { + return insertGroupCapacity(GroupCapacityPersistService.CLUSTER); + } + // Group会自动扩容 + return initGroupCapacity(group); + } + + private boolean insertGroupCapacity(String group) { + return insertGroupCapacity(group, null, null, null, null); + } + + private boolean insertGroupCapacity(String group, Integer quota, Integer maxSize, Integer maxAggrCount, + Integer maxAggrSize) { + try { + final Timestamp now = TimeUtils.getCurrentTime(); + GroupCapacity groupCapacity = new GroupCapacity(); + groupCapacity.setGroup(group); + // 新增时,quota=0表示限额为默认值,为了在更新默认限额时只需修改nacos配置,而不需要更新表中大部分数据 + groupCapacity.setQuota(quota == null ? ZERO : quota); + // 新增时,maxSize=0表示大小为默认值,为了在更新默认大小时只需修改nacos配置,而不需要更新表中大部分数据 + groupCapacity.setMaxSize(maxSize == null ? ZERO : maxSize); + groupCapacity.setMaxAggrCount(maxAggrCount == null ? ZERO : maxAggrCount); + groupCapacity.setMaxAggrSize(maxAggrSize == null ? ZERO : maxAggrSize); + groupCapacity.setGmtCreate(now); + groupCapacity.setGmtModified(now); + return groupCapacityPersistService.insertGroupCapacity(groupCapacity); + } catch (DuplicateKeyException e) { + // 并发情况下同时insert会出现,ignore + LogUtil.defaultLog.warn("group: {}, message: {}", group, e.getMessage()); + } + return false; + } + + private boolean updateGroupUsage(CounterMode counterMode, String group, int defaultQuota, + boolean ignoreQuotaLimit) { + final Timestamp now = TimeUtils.getCurrentTime(); + GroupCapacity groupCapacity = new GroupCapacity(); + groupCapacity.setGroup(group); + groupCapacity.setQuota(defaultQuota); + groupCapacity.setGmtModified(now); + if (CounterMode.INCREMENT == counterMode) { + if (ignoreQuotaLimit) { + return groupCapacityPersistService.incrementUsage(groupCapacity); + } + // 先按默认值限额更新,大部分情况下都是默认值,默认值表里面的quota字段为0 + return groupCapacityPersistService.incrementUsageWithDefaultQuotaLimit(groupCapacity) + || groupCapacityPersistService.incrementUsageWithQuotaLimit(groupCapacity); + } + return groupCapacityPersistService.decrementUsage(groupCapacity); + } + + /** + * 提供给关闭容量管理的限制检验功能时计数使用
租户: 1. 如果容量信息不存在,则初始化容量信息
2. 更新容量的使用量usage,加一或减一 + * + * @param counterMode 增加或者减少 + * @param tenant 租户 + * @param ignoreQuotaLimit 是否忽略容量额度限制,在关闭容量管理的限制检验功能只计数的时候为true,开启容量管理的限制检验功能则为false + * @return 是否操作成功 + */ + public boolean insertAndUpdateTenantUsage(CounterMode counterMode, String tenant, boolean ignoreQuotaLimit) { + TenantCapacity tenantCapacity = getTenantCapacity(tenant); + if (tenantCapacity == null) { + // 初始化容量信息 + initTenantCapacity(tenant); + } + return updateTenantUsage(counterMode, tenant, ignoreQuotaLimit); + } + + private boolean updateTenantUsage(CounterMode counterMode, String tenant, boolean ignoreQuotaLimit) { + final Timestamp now = TimeUtils.getCurrentTime(); + TenantCapacity tenantCapacity = new TenantCapacity(); + tenantCapacity.setTenant(tenant); + tenantCapacity.setQuota(PropertyUtil.getDefaultTenantQuota()); + tenantCapacity.setGmtModified(now); + if (CounterMode.INCREMENT == counterMode) { + if (ignoreQuotaLimit) { + return tenantCapacityPersistService.incrementUsage(tenantCapacity); + } + // 先按默认值限额更新,大部分情况下都是默认值,默认值表里面的quota字段为0 + return tenantCapacityPersistService.incrementUsageWithDefaultQuotaLimit(tenantCapacity) + || tenantCapacityPersistService.incrementUsageWithQuotaLimit(tenantCapacity); + } + return tenantCapacityPersistService.decrementUsage(tenantCapacity); + } + + public boolean updateTenantUsage(CounterMode counterMode, String tenant) { + return updateTenantUsage(counterMode, tenant, false); + } + + /** + * 初始化该租户的容量信息,如果到达限额,将自动扩容,以降低运维成本 + */ + public boolean initTenantCapacity(String tenant) { + return initTenantCapacity(tenant, null, null, null, null); + } + + /** + * 初始化该租户的容量信息,如果到达限额,将自动扩容,以降低运维成本 + */ + public boolean initTenantCapacity(String tenant, Integer quota, Integer maxSize, Integer maxAggrCount, + Integer maxAggrSize) { + boolean insertSuccess = insertTenantCapacity(tenant, quota, maxSize, maxAggrCount, maxAggrSize); + if (quota != null) { + return insertSuccess; + } + autoExpansion(null, tenant); + return insertSuccess; + } + + private boolean insertTenantCapacity(String tenant) { + return insertTenantCapacity(tenant, null, null, null, null); + } + + private boolean insertTenantCapacity(String tenant, Integer quota, Integer maxSize, Integer maxAggrCount, + Integer maxAggrSize) { + try { + final Timestamp now = TimeUtils.getCurrentTime(); + TenantCapacity tenantCapacity = new TenantCapacity(); + tenantCapacity.setTenant(tenant); + // 新增时,quota=0表示限额为默认值,为了在更新默认限额时只需修改nacos配置,而不需要更新表中大部分数据 + tenantCapacity.setQuota(quota == null ? ZERO : quota); + // 新增时,maxSize=0表示大小为默认值,为了在更新默认大小时只需修改nacos配置,而不需要更新表中大部分数据 + tenantCapacity.setMaxSize(maxSize == null ? ZERO : maxSize); + tenantCapacity.setMaxAggrCount(maxAggrCount == null ? ZERO : maxAggrCount); + tenantCapacity.setMaxAggrSize(maxAggrSize == null ? ZERO : maxAggrSize); + tenantCapacity.setGmtCreate(now); + tenantCapacity.setGmtModified(now); + return tenantCapacityPersistService.insertTenantCapacity(tenantCapacity); + } catch (DuplicateKeyException e) { + // 并发情况下同时insert会出现,ignore + LogUtil.defaultLog.warn("tenant: {}, message: {}", tenant, e.getMessage()); + } + return false; + } + + public TenantCapacity getTenantCapacity(String tenant) { + return tenantCapacityPersistService.getTenantCapacity(tenant); + } + + /** + * 提供给API接口使用
租户:记录不存在则初始化,存在则直接更新容量限额或者内容大小 + * + * @param group Group ID + * @param tenant 租户 + * @param quota 容量限额 + * @param maxSize 配置内容(content)大小限制 + * @return 是否操作成功 + */ + public boolean insertOrUpdateCapacity(String group, String tenant, Integer quota, Integer maxSize, Integer + maxAggrCount, Integer maxAggrSize) { + if (StringUtils.isNotBlank(tenant)) { + Capacity capacity = tenantCapacityPersistService.getTenantCapacity(tenant); + if (capacity == null) { + return initTenantCapacity(tenant, quota, maxSize, maxAggrCount, maxAggrSize); + } + return tenantCapacityPersistService.updateTenantCapacity(tenant, quota, maxSize, maxAggrCount, + maxAggrSize); + } + Capacity capacity = groupCapacityPersistService.getGroupCapacity(group); + if (capacity == null) { + return initGroupCapacity(group, quota, maxSize, maxAggrCount, maxAggrSize); + } + return groupCapacityPersistService.updateGroupCapacity(group, quota, maxSize, maxAggrCount, maxAggrSize); + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/capacity/GroupCapacityPersistService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/capacity/GroupCapacityPersistService.java new file mode 100644 index 00000000000..3d85f49e47d --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/capacity/GroupCapacityPersistService.java @@ -0,0 +1,327 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service.capacity; + +import com.alibaba.nacos.config.server.model.capacity.Capacity; +import com.alibaba.nacos.config.server.model.capacity.GroupCapacity; +import com.alibaba.nacos.config.server.service.BasicDataSourceServiceImpl; +import com.alibaba.nacos.config.server.service.DataSourceService; +import com.alibaba.nacos.config.server.service.DynamicDataSource; +import com.alibaba.nacos.config.server.service.LocalDataSourceServiceImpl; +import com.alibaba.nacos.config.server.utils.PropertyUtil; +import com.alibaba.nacos.config.server.utils.TimeUtils; +import com.google.common.collect.Lists; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.CannotGetJdbcConnectionException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.PreparedStatementCreator; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.stereotype.Service; + +import static com.alibaba.nacos.config.server.utils.LogUtil.fatalLog; + +import java.sql.*; +import java.util.List; + +import javax.annotation.PostConstruct; + +/** + * Group Capacity Service + * @author hexu.hxy + * @date 2018/03/05 + */ +@Service +public class GroupCapacityPersistService { + static final String CLUSTER = ""; + + private static final GroupCapacityRowMapper + GROUP_CAPACITY_ROW_MAPPER = new GroupCapacityRowMapper(); + private JdbcTemplate jdbcTemplate; + + @Autowired + private DynamicDataSource dynamicDataSource; + private DataSourceService dataSourceService; + + @PostConstruct + public void init() { + this.dataSourceService = dynamicDataSource.getDataSource(); + this.jdbcTemplate = dataSourceService.getJdbcTemplate(); + } + + private static final class GroupCapacityRowMapper implements + RowMapper { + @Override + public GroupCapacity mapRow(ResultSet rs, int rowNum) throws SQLException { + GroupCapacity groupCapacity = new GroupCapacity(); + groupCapacity.setId(rs.getLong("id")); + groupCapacity.setQuota(rs.getInt("quota")); + groupCapacity.setUsage(rs.getInt("usage")); + groupCapacity.setMaxSize(rs.getInt("max_size")); + groupCapacity.setMaxAggrCount(rs.getInt("max_aggr_count")); + groupCapacity.setMaxAggrSize(rs.getInt("max_aggr_size")); + groupCapacity.setGroup(rs.getString("group_id")); + return groupCapacity; + } + } + + public GroupCapacity getGroupCapacity(String groupId) { + String sql + = "select id, quota, `usage`, `max_size`, max_aggr_count, max_aggr_size, group_id from group_capacity " + + "where group_id=?"; + List list = jdbcTemplate.query(sql, new Object[] {groupId}, + GROUP_CAPACITY_ROW_MAPPER); + if (list.isEmpty()) { + return null; + } + return list.get(0); + } + + public Capacity getClusterCapacity() { + return getGroupCapacity(CLUSTER); + } + + public boolean insertGroupCapacity(final GroupCapacity capacity) { + String sql; + if (CLUSTER.equals(capacity.getGroup())) { + sql + = "insert into group_capacity (group_id, quota, `usage`, `max_size`, max_aggr_count, max_aggr_size, " + + "gmt_create, gmt_modified) select ?, ?, count(*), ?, ?, ?, ?, ? from config_info;"; + } else { + // 注意这里要加"tenant_id = ''"条件 + sql = + "insert into group_capacity (group_id, quota, `usage`, `max_size`, max_aggr_count, max_aggr_size, " + + "gmt_create, gmt_modified) select ?, ?, count(*), ?, ?, ?, ?, ? from config_info where " + + "group_id=? and tenant_id = '';"; + } + return insertGroupCapacity(sql, capacity); + } + + public int getClusterUsage() { + Capacity clusterCapacity = getClusterCapacity(); + if (clusterCapacity != null) { + return clusterCapacity.getUsage(); + } + String sql = "select count(*) from config_info"; + Integer result = jdbcTemplate.queryForObject(sql, Integer.class); + if (result ==null) { + throw new IllegalArgumentException("configInfoCount error"); + } + return result.intValue(); + } + + private boolean insertGroupCapacity(final String sql, final GroupCapacity capacity) { + try { + GeneratedKeyHolder generatedKeyHolder = new GeneratedKeyHolder(); + PreparedStatementCreator preparedStatementCreator = new PreparedStatementCreator() { + @Override + @SuppressFBWarnings(value = { "OBL_UNSATISFIED_OBLIGATION_EXCEPTION_EDGE", + "SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING" }, justification = "findbugs does not trust jdbctemplate, sql is constant in practice") + public PreparedStatement createPreparedStatement(Connection connection) throws SQLException { + PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); + String group = capacity.getGroup(); + ps.setString(1, group); + ps.setInt(2, capacity.getQuota()); + ps.setInt(3, capacity.getMaxSize()); + ps.setInt(4, capacity.getMaxAggrCount()); + ps.setInt(5, capacity.getMaxAggrSize()); + ps.setTimestamp(6, capacity.getGmtCreate()); + ps.setTimestamp(7, capacity.getGmtModified()); + if (!CLUSTER.equals(group)) { + ps.setString(8, group); + } + return ps; + } + }; + jdbcTemplate.update(preparedStatementCreator, generatedKeyHolder); + return generatedKeyHolder.getKey() != null; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error]", e); + throw e; + } + } + + public boolean incrementUsageWithDefaultQuotaLimit(GroupCapacity groupCapacity) { + String sql = + "update group_capacity set `usage` = `usage` + 1, gmt_modified = ? where group_id = ? and `usage` <" + + " ? and quota = 0"; + try { + int affectRow = jdbcTemplate.update(sql, + groupCapacity.getGmtModified(), groupCapacity.getGroup(), groupCapacity.getQuota()); + return affectRow == 1; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error]", e); + throw e; + } + } + + public boolean incrementUsageWithQuotaLimit(GroupCapacity groupCapacity) { + String sql + = "update group_capacity set `usage` = `usage` + 1, gmt_modified = ? where group_id = ? and `usage` < " + + "quota and quota != 0"; + try { + return jdbcTemplate.update(sql, + groupCapacity.getGmtModified(), groupCapacity.getGroup()) == 1; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error]", e); + throw e; + + } + } + + public boolean incrementUsage(GroupCapacity groupCapacity) { + String sql = "update group_capacity set `usage` = `usage` + 1, gmt_modified = ? where group_id = ?"; + try { + int affectRow = jdbcTemplate.update(sql, + groupCapacity.getGmtModified(), groupCapacity.getGroup()); + return affectRow == 1; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error]", e); + throw e; + } + } + + public boolean decrementUsage(GroupCapacity groupCapacity) { + String sql = + "update group_capacity set `usage` = `usage` - 1, gmt_modified = ? where group_id = ? and `usage` > 0"; + try { + return jdbcTemplate.update(sql, + groupCapacity.getGmtModified(), groupCapacity.getGroup()) == 1; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error]", e); + throw e; + } + } + + public boolean updateGroupCapacity(String group, Integer quota, Integer maxSize, Integer maxAggrCount, + Integer maxAggrSize) { + List argList = Lists.newArrayList(); + StringBuilder sql = new StringBuilder("update group_capacity set"); + if (quota != null) { + sql.append(" quota = ?,"); + argList.add(quota); + } + if (maxSize != null) { + sql.append(" max_size = ?,"); + argList.add(maxSize); + } + if (maxAggrCount != null) { + sql.append(" max_aggr_count = ?,"); + argList.add(maxAggrCount); + } + if (maxAggrSize != null) { + sql.append(" max_aggr_size = ?,"); + argList.add(maxAggrSize); + } + sql.append(" gmt_modified = ?"); + argList.add(TimeUtils.getCurrentTime()); + + sql.append(" where group_id = ?"); + argList.add(group); + try { + return jdbcTemplate.update(sql.toString(), argList.toArray()) == 1; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error]", e); + throw e; + } + } + + public boolean updateQuota(String group, Integer quota) { + return updateGroupCapacity(group, quota, null, null, null); + } + + public boolean updateMaxSize(String group, Integer maxSize) { + return updateGroupCapacity(group, null, maxSize, null, null); + } + + public boolean correctUsage(String group, Timestamp gmtModified) { + String sql; + if (CLUSTER.equals(group)) { + sql = "update group_capacity set `usage` = (select count(*) from config_info), gmt_modified = ? where " + + "group_id = ?"; + try { + return jdbcTemplate.update(sql, gmtModified, group) == 1; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error]", e); + throw e; + } + } else { + // 注意这里要加"tenant_id = ''"条件 + sql = "update group_capacity set `usage` = (select count(*) from config_info where group_id=? and " + + "tenant_id = ''), gmt_modified = ? where group_id = ?"; + try { + return jdbcTemplate.update(sql, group, gmtModified, group) == 1; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error]", e); + throw e; + } + } + } + + /** + * 获取GroupCapacity列表,只有id、groupId有值 + * + * @param lastId id > lastId + * @param pageSize 页数 + * @return GroupCapacity列表 + */ + public List getCapacityList4CorrectUsage(long lastId, int pageSize) { + String sql = "select id, group_id from group_capacity where id>? limit ?"; + + if (PropertyUtil.isStandaloneMode()) { + sql = "select id, group_id from group_capacity where id>? OFFSET 0 ROWS FETCH NEXT ? ROWS ONLY"; + } + try { + return jdbcTemplate.query(sql, new Object[] {lastId, pageSize}, + new RowMapper() { + @Override + public GroupCapacity mapRow(ResultSet rs, int rowNum) throws SQLException { + GroupCapacity groupCapacity = new GroupCapacity(); + groupCapacity.setId(rs.getLong("id")); + groupCapacity.setGroup(rs.getString("group_id")); + return groupCapacity; + } + }); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error]", e); + throw e; + } + } + + public boolean deleteGroupCapacity(final String group) { + try { + PreparedStatementCreator preparedStatementCreator = new PreparedStatementCreator() { + @Override + @SuppressFBWarnings(value = { "OBL_UNSATISFIED_OBLIGATION_EXCEPTION_EDGE", + "SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING" }, justification = "findbugs does not trust jdbctemplate, sql is constant in practice") + public PreparedStatement createPreparedStatement(Connection connection) throws SQLException { + PreparedStatement ps = connection.prepareStatement( + "delete from group_capacity where group_id = ?;"); + ps.setString(1, group); + return ps; + } + }; + return jdbcTemplate.update(preparedStatementCreator) == 1; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error]", e); + throw e; + } + + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/capacity/TenantCapacityPersistService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/capacity/TenantCapacityPersistService.java new file mode 100644 index 00000000000..3f3c2562195 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/capacity/TenantCapacityPersistService.java @@ -0,0 +1,277 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service.capacity; + +import com.alibaba.nacos.config.server.model.capacity.TenantCapacity; +import com.alibaba.nacos.config.server.service.BasicDataSourceServiceImpl; +import com.alibaba.nacos.config.server.service.DataSourceService; +import com.alibaba.nacos.config.server.service.DynamicDataSource; +import com.alibaba.nacos.config.server.service.LocalDataSourceServiceImpl; +import com.alibaba.nacos.config.server.utils.PropertyUtil; +import com.alibaba.nacos.config.server.utils.TimeUtils; +import com.google.common.collect.Lists; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.CannotGetJdbcConnectionException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.PreparedStatementCreator; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.stereotype.Service; + +import static com.alibaba.nacos.config.server.utils.LogUtil.fatalLog; + +import java.sql.*; +import java.util.List; + +import javax.annotation.PostConstruct; + +/** + * Tenant Capacity Service + * @author hexu.hxy + * @date 2018/03/05 + */ +@Service +public class TenantCapacityPersistService { + + private static final TenantCapacityRowMapper + TENANT_CAPACITY_ROW_MAPPER = new TenantCapacityRowMapper(); + private JdbcTemplate jdbcTemplate; + + @Autowired + private DynamicDataSource dynamicDataSource; + private DataSourceService dataSourceService; + + @PostConstruct + public void init() { + this.dataSourceService = dynamicDataSource.getDataSource(); + this.jdbcTemplate = dataSourceService.getJdbcTemplate(); + } + + private static final class TenantCapacityRowMapper implements + RowMapper { + @Override + public TenantCapacity mapRow(ResultSet rs, int rowNum) throws SQLException { + TenantCapacity tenantCapacity = new TenantCapacity(); + tenantCapacity.setId(rs.getLong("id")); + tenantCapacity.setQuota(rs.getInt("quota")); + tenantCapacity.setUsage(rs.getInt("usage")); + tenantCapacity.setMaxSize(rs.getInt("max_size")); + tenantCapacity.setMaxAggrCount(rs.getInt("max_aggr_count")); + tenantCapacity.setMaxAggrSize(rs.getInt("max_aggr_size")); + tenantCapacity.setTenant(rs.getString("tenant_id")); + return tenantCapacity; + } + } + + public TenantCapacity getTenantCapacity(String tenantId) { + String sql + = "select id, quota, `usage`, `max_size`, max_aggr_count, max_aggr_size, tenant_id from tenant_capacity " + + "where tenant_id=?"; + List list = jdbcTemplate.query(sql, new Object[] {tenantId}, + TENANT_CAPACITY_ROW_MAPPER); + if (list.isEmpty()) { + return null; + } + return list.get(0); + } + + public boolean insertTenantCapacity(final TenantCapacity tenantCapacity) { + final String sql = + "insert into tenant_capacity (tenant_id, quota, `usage`, `max_size`, max_aggr_count, max_aggr_size, " + + "gmt_create, gmt_modified) select ?, ?, count(*), ?, ?, ?, ?, ? from config_info where tenant_id=?;"; + try { + GeneratedKeyHolder generatedKeyHolder = new GeneratedKeyHolder(); + PreparedStatementCreator preparedStatementCreator = new PreparedStatementCreator() { + @Override + @SuppressFBWarnings(value = { "OBL_UNSATISFIED_OBLIGATION_EXCEPTION_EDGE", + "SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING" }, justification = "findbugs does not trust jdbctemplate, sql is constant in practice") + public PreparedStatement createPreparedStatement(Connection connection) throws SQLException { + PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); + String tenant = tenantCapacity.getTenant(); + ps.setString(1, tenant); + ps.setInt(2, tenantCapacity.getQuota()); + ps.setInt(3, tenantCapacity.getMaxSize()); + ps.setInt(4, tenantCapacity.getMaxAggrCount()); + ps.setInt(5, tenantCapacity.getMaxAggrSize()); + ps.setTimestamp(6, tenantCapacity.getGmtCreate()); + ps.setTimestamp(7, tenantCapacity.getGmtModified()); + ps.setString(8, tenantCapacity.getTenant()); + return ps; + } + }; + jdbcTemplate.update(preparedStatementCreator, generatedKeyHolder); + return generatedKeyHolder.getKey() != null; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error]", e); + throw e; + } + + } + + public boolean incrementUsageWithDefaultQuotaLimit(TenantCapacity tenantCapacity) { + String sql = + "update tenant_capacity set `usage` = `usage` + 1, gmt_modified = ? where tenant_id = ? and `usage` <" + + " ? and quota = 0"; + try { + int affectRow = jdbcTemplate.update(sql, + tenantCapacity.getGmtModified(), tenantCapacity.getTenant(), tenantCapacity.getQuota()); + return affectRow == 1; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error]", e); + throw e; + } + } + + public boolean incrementUsageWithQuotaLimit(TenantCapacity tenantCapacity) { + String sql + = "update tenant_capacity set `usage` = `usage` + 1, gmt_modified = ? where tenant_id = ? and `usage` < " + + "quota and quota != 0"; + try { + return jdbcTemplate.update(sql, + tenantCapacity.getGmtModified(), tenantCapacity.getTenant()) == 1; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error]", e); + throw e; + + } + } + + public boolean incrementUsage(TenantCapacity tenantCapacity) { + String sql = "update tenant_capacity set `usage` = `usage` + 1, gmt_modified = ? where tenant_id = ?"; + try { + int affectRow = jdbcTemplate.update(sql, + tenantCapacity.getGmtModified(), tenantCapacity.getTenant()); + return affectRow == 1; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error]", e); + throw e; + } + } + + public boolean decrementUsage(TenantCapacity tenantCapacity) { + String sql = + "update tenant_capacity set `usage` = `usage` - 1, gmt_modified = ? where tenant_id = ? and `usage` > 0"; + try { + return jdbcTemplate.update(sql, + tenantCapacity.getGmtModified(), tenantCapacity.getTenant()) == 1; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error]", e); + throw e; + } + } + + public boolean updateTenantCapacity(String tenant, Integer quota, Integer maxSize, Integer maxAggrCount, + Integer maxAggrSize) { + List argList = Lists.newArrayList(); + StringBuilder sql = new StringBuilder("update tenant_capacity set"); + if (quota != null) { + sql.append(" quota = ?,"); + argList.add(quota); + } + if (maxSize != null) { + sql.append(" max_size = ?,"); + argList.add(maxSize); + } + if (maxAggrCount != null) { + sql.append(" max_aggr_count = ?,"); + argList.add(maxAggrCount); + } + if (maxAggrSize != null) { + sql.append(" max_aggr_size = ?,"); + argList.add(maxAggrSize); + } + sql.append(" gmt_modified = ?"); + argList.add(TimeUtils.getCurrentTime()); + + sql.append(" where tenant_id = ?"); + argList.add(tenant); + try { + return jdbcTemplate.update(sql.toString(), argList.toArray()) == 1; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error]", e); + throw e; + } + } + + public boolean updateQuota(String tenant, Integer quota) { + return updateTenantCapacity(tenant, quota, null, null, null); + } + + public boolean correctUsage(String tenant, Timestamp gmtModified) { + String sql = "update tenant_capacity set `usage` = (select count(*) from config_info where tenant_id = ?), " + + "gmt_modified = ? where tenant_id = ?"; + try { + return jdbcTemplate.update(sql, tenant, gmtModified, tenant) == 1; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error]", e); + throw e; + } + } + + /** + * 获取TenantCapacity列表,只有id、tenantId有值 + * + * @param lastId id > lastId + * @param pageSize 页数 + * @return TenantCapacity列表 + */ + public List getCapacityList4CorrectUsage(long lastId, int pageSize) { + String sql = "select id, tenant_id from tenant_capacity where id>? limit ?"; + + if (PropertyUtil.isStandaloneMode()) { + sql = "select id, tenant_id from tenant_capacity where id>? OFFSET 0 ROWS FETCH NEXT ? ROWS ONLY"; + } + + try { + return jdbcTemplate.query(sql, new Object[] {lastId, pageSize}, + new RowMapper() { + @Override + public TenantCapacity mapRow(ResultSet rs, int rowNum) throws SQLException { + TenantCapacity tenantCapacity = new TenantCapacity(); + tenantCapacity.setId(rs.getLong("id")); + tenantCapacity.setTenant(rs.getString("tenant_id")); + return tenantCapacity; + } + }); + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error]", e); + throw e; + } + } + + public boolean deleteTenantCapacity(final String tenant) { + try { + PreparedStatementCreator preparedStatementCreator = new PreparedStatementCreator() { + @Override + @SuppressFBWarnings(value = { "OBL_UNSATISFIED_OBLIGATION_EXCEPTION_EDGE", + "SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING" }, justification = "findbugs does not trust jdbctemplate, sql is constant in practice") + public PreparedStatement createPreparedStatement(Connection connection) throws SQLException { + PreparedStatement ps = connection.prepareStatement( + "delete from tenant_capacity where tenant_id = ?;"); + ps.setString(1, tenant); + return ps; + } + }; + return jdbcTemplate.update(preparedStatementCreator) == 1; + } catch (CannotGetJdbcConnectionException e) { + fatalLog.error("[db-error]", e); + throw e; + } + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/DumpService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/DumpService.java new file mode 100755 index 00000000000..5c17497b3e7 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/DumpService.java @@ -0,0 +1,403 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service.dump; + +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.manager.TaskManager; +import com.alibaba.nacos.config.server.model.ConfigInfo; +import com.alibaba.nacos.config.server.model.ConfigInfoAggr; +import com.alibaba.nacos.config.server.model.ConfigInfoChanged; +import com.alibaba.nacos.config.server.model.Page; +import com.alibaba.nacos.config.server.service.ConfigService; +import com.alibaba.nacos.config.server.service.DiskUtil; +import com.alibaba.nacos.config.server.service.PersistService; +import com.alibaba.nacos.config.server.service.ServerListService; +import com.alibaba.nacos.config.server.service.TimerTaskService; +import com.alibaba.nacos.config.server.service.PersistService.ConfigInfoWrapper; +import com.alibaba.nacos.config.server.service.merge.MergeTaskProcessor; +import com.alibaba.nacos.config.server.utils.*; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Service; + +import static com.alibaba.nacos.config.server.utils.LogUtil.fatalLog; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.URI; +import java.net.URL; +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.Properties; +import java.util.Random; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.annotation.PostConstruct; + +/** + * Dump data service + * @author Nacos + * + */ +@Service +public class DumpService { + + @Autowired + private Environment env; + + @Autowired + PersistService persistService; + + @PostConstruct + public void init() { + LogUtil.defaultLog.warn("DumpService start"); + DumpProcessor processor = new DumpProcessor(this); + DumpAllProcessor dumpAllProcessor = new DumpAllProcessor(this); + DumpAllBetaProcessor dumpAllBetaProcessor = new DumpAllBetaProcessor(this); + DumpAllTagProcessor dumpAllTagProcessor = new DumpAllTagProcessor(this); + + dumpTaskMgr = new TaskManager( + "com.alibaba.nacos.server.DumpTaskManager"); + dumpTaskMgr.setDefaultTaskProcessor(processor); + + dumpAllTaskMgr = new TaskManager( + "com.alibaba.nacos.server.DumpAllTaskManager"); + dumpAllTaskMgr.setDefaultTaskProcessor(dumpAllProcessor); + + Runnable dumpAll = new Runnable() { + @Override + public void run() { + dumpAllTaskMgr.addTask(DumpAllTask.TASK_ID, new DumpAllTask()); + } + }; + + Runnable dumpAllBeta = new Runnable() { + @Override + public void run() { + dumpAllTaskMgr.addTask(DumpAllBetaTask.TASK_ID, new DumpAllBetaTask()); + } + }; + + Runnable clearConfigHistory = new Runnable() { + @Override + public void run() { + log.warn("clearConfigHistory start"); + if (ServerListService.isFirstIp()) { + try { + Timestamp startTime = getBeforeStamp(TimeUtils.getCurrentTime(), 24 * 30); + int totalCount = persistService.findConfigHistoryCountByTime(startTime); + if (totalCount > 0) { + int pageSize = 1000; + int removeTime = (totalCount + pageSize - 1) / pageSize; + log.warn("clearConfigHistory, getBeforeStamp:{}, totalCount:{}, pageSize:{}, removeTime:{}", + new Object[] { startTime, totalCount, pageSize, removeTime }); + while (removeTime > 0) { + // 分页删除,以免批量太大报错 + persistService.removeConfigHistory(startTime, pageSize); + removeTime--; + } + } + } catch (Throwable e) { + log.error("clearConfigHistory error", e); + } + } + } + }; + + try { + dumpConfigInfo(dumpAllProcessor); + + // 更新beta缓存 + LogUtil.defaultLog.info("start clear all config-info-beta."); + DiskUtil.clearAllBeta(); + if (persistService.isExistTable(BETA_TABLE_NAME)) { + dumpAllBetaProcessor.process(DumpAllBetaTask.TASK_ID, new DumpAllBetaTask()); + } + // 更新Tag缓存 + LogUtil.defaultLog.info("start clear all config-info-tag."); + DiskUtil.clearAllTag(); + if (persistService.isExistTable(TAG_TABLE_NAME)) { + dumpAllTagProcessor.process(DumpAllTagTask.TASK_ID, new DumpAllTagTask()); + } + + // add to dump aggr + List configList = persistService.findAllAggrGroup(); + if (configList != null && !configList.isEmpty()) { + total = configList.size(); + List> splitList = splitList(configList, INIT_THREAD_COUNT); + for (List list : splitList) { + MergeAllDataWorker work = new MergeAllDataWorker(list); + work.start(); + } + log.info("server start, schedule merge end."); + } + } catch (Exception e) { + LogUtil.fatalLog.error( + "Nacos Server did not start because dumpservice bean construction failure :\n" + e.getMessage(), + e.getCause()); + throw new RuntimeException( + "Nacos Server did not start because dumpservice bean construction failure :\n" + e.getMessage()); + } + if (!PropertyUtil.isStandaloneMode()) { + Runnable heartbeat = new Runnable() { + @Override + public void run() { + String heartBeatTime = TimeUtils.getCurrentTime().toString(); + // write disk + try { + DiskUtil.saveHeartBeatToDisk(heartBeatTime); + } catch (IOException e) { + LogUtil.fatalLog.error("save heartbeat fail" + e.getMessage()); + } + } + }; + + TimerTaskService.scheduleWithFixedDelay(heartbeat, 0, 10, TimeUnit.SECONDS); + + long initialDelay = new Random().nextInt(INITIAL_DELAY_IN_MINUTE) + 10; + LogUtil.defaultLog.warn("initialDelay:{}", initialDelay); + + TimerTaskService.scheduleWithFixedDelay(dumpAll, initialDelay, DUMP_ALL_INTERVAL_IN_MINUTE, TimeUnit.MINUTES); + + TimerTaskService.scheduleWithFixedDelay(dumpAllBeta, initialDelay, DUMP_ALL_INTERVAL_IN_MINUTE, TimeUnit.MINUTES); + } + + TimerTaskService.scheduleWithFixedDelay(clearConfigHistory, 10, 10, TimeUnit.MINUTES); + + } + + private void dumpConfigInfo(DumpAllProcessor dumpAllProcessor) + throws IOException { + int timeStep = 6; + Boolean isAllDump = true; + // initial dump all + FileInputStream fis = null; + Timestamp heartheatLastStamp = null; + try { + if (isQuickStart()) { + File heartbeatFile = DiskUtil.heartBeatFile(); + if (heartbeatFile.exists()) { + fis = new FileInputStream(heartbeatFile); + String heartheatTempLast = IOUtils.toString(fis, + Constants.ENCODE); + heartheatLastStamp = Timestamp.valueOf(heartheatTempLast); + if (TimeUtils.getCurrentTime().getTime() + - heartheatLastStamp.getTime() < timeStep * 60 * 60 * 1000) { + isAllDump = false; + } + } + } + if (isAllDump) { + LogUtil.defaultLog.info("start clear all config-info."); + DiskUtil.clearAll(); + dumpAllProcessor.process(DumpAllTask.TASK_ID, new DumpAllTask()); + } else { + Timestamp beforeTimeStamp = getBeforeStamp(heartheatLastStamp, + timeStep); + DumpChangeProcessor dumpChangeProcessor = new DumpChangeProcessor( + this, beforeTimeStamp, TimeUtils.getCurrentTime()); + dumpChangeProcessor.process(DumpChangeTask.TASK_ID, + new DumpChangeTask()); + Runnable checkMd5Task = new Runnable() { + @Override + public void run() { + LogUtil.defaultLog.error("start checkMd5Task"); + List diffList = ConfigService.checkMd5(); + for (String groupKey : diffList) { + String[] dg = GroupKey.parseKey(groupKey); + String dataId = dg[0]; + String group = dg[1]; + String tenant = dg[2]; + ConfigInfoWrapper configInfo = persistService.queryConfigInfo(dataId, group, tenant); + ConfigService.dumpChange(dataId, group, tenant, configInfo.getContent(), + configInfo.getLastModified()); + } + LogUtil.defaultLog.error("end checkMd5Task"); + } + }; + TimerTaskService.scheduleWithFixedDelay(checkMd5Task, 0, 12, + TimeUnit.HOURS); + } + } catch (IOException e) { + LogUtil.fatalLog.error("dump config fail" + e.getMessage()); + throw e; + } finally { + if (null != fis) { + try { + fis.close(); + } catch (IOException e) { + LogUtil.defaultLog.warn("close file failed"); + } + } + } + } + + private Timestamp getBeforeStamp(Timestamp date , int step) { + Calendar cal = Calendar.getInstance(); + /** + * date 换成已经已知的Date对象 + */ + cal.setTime(date); + /** + * before 6 hour + */ + cal.add(Calendar.HOUR_OF_DAY, -step); + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + return Timestamp.valueOf(format.format(cal.getTime())); + } + + private Boolean isQuickStart() + { + try { + String val = null; + val = env.getProperty("isQuickStart"); + if (val != null && TRUE_STR.equals(val)) { + isQuickStart = true; + } + fatalLog.warn("isQuickStart:{}", isQuickStart); + } catch (Exception e) { + fatalLog.error("read application.properties wrong", e); + } + return isQuickStart; + } + + public void dump(String dataId, String group, String tenant, String tag, long lastModified, String handleIp) { + dump(dataId, group, tenant, tag, lastModified, handleIp, false); + } + + public void dump(String dataId, String group, String tenant, long lastModified, String handleIp) { + dump(dataId, group, tenant, lastModified, handleIp, false); + } + + public void dump(String dataId, String group, String tenant, long lastModified, String handleIp, boolean isBeta) { + String groupKey = GroupKey2.getKey(dataId, group, tenant); + dumpTaskMgr.addTask(groupKey, new DumpTask(groupKey, lastModified, handleIp, isBeta)); + } + + public void dump(String dataId, String group, String tenant, String tag, long lastModified, String handleIp, + boolean isBeta) { + String groupKey = GroupKey2.getKey(dataId, group, tenant); + dumpTaskMgr.addTask(groupKey, new DumpTask(groupKey, tag, lastModified, handleIp, isBeta)); + } + + public void dumpAll() { + dumpAllTaskMgr.addTask(DumpAllTask.TASK_ID, new DumpAllTask()); + } + static List> splitList(List list, int count){ + List> result = new ArrayList>(count); + for(int i = 0 ; i < count ; i++){ + result.add(new ArrayList()); + } + for(int i = 0; i < list.size() ; i++){ + ConfigInfoChanged config = list.get(i); + result.get(i % count).add(config); + } + return result; + } + + class MergeAllDataWorker extends Thread{ + static final int PAGE_SIZE = 10000; + + private List configInfoList; + public MergeAllDataWorker(List configInfoList) { + super("MergeAllDataWorker"); + this.configInfoList = configInfoList; + } + + @Override + public void run() { + for (ConfigInfoChanged configInfo : configInfoList) { + String dataId = configInfo.getDataId(); + String group = configInfo.getGroup(); + String tenant = configInfo.getTenant(); + try { + List datumList = new ArrayList(); + int rowCount = persistService.aggrConfigInfoCount(dataId, group, tenant); + int pageCount = (int) Math.ceil(rowCount * 1.0 / PAGE_SIZE); + for (int pageNo = 1; pageNo <= pageCount; pageNo++) { + Page page = persistService.findConfigInfoAggrByPage(dataId, group, tenant, pageNo, PAGE_SIZE); + if (page != null) { + datumList.addAll(page.getPageItems()); + log.info("[merge-query] {}, {}, size/total={}/{}", new Object[] { dataId, group, datumList.size(), rowCount }); + } + } + + final Timestamp time = TimeUtils.getCurrentTime(); + // 聚合 + if (datumList.size() > 0) { + ConfigInfo cf = MergeTaskProcessor.merge(dataId, group, tenant, datumList); + String aggrContent = cf.getContent(); + String localContentMD5 = ConfigService.getContentMd5(GroupKey.getKey(dataId, group)); + String aggrConetentMD5 = MD5.getInstance().getMD5String(aggrContent); + if(!StringUtils.equals(localContentMD5, aggrConetentMD5)){ + persistService.insertOrUpdate(null, null, cf, time, null, false); + log.info("[merge-ok] {}, {}, size={}, length={}, md5={}, content={}", new Object[] { dataId, group, datumList.size(), + cf.getContent().length(), cf.getMd5(), ContentUtils.truncateContent(cf.getContent()) }); + } + } + // 删除 + else { + persistService.removeConfigInfo(dataId, group, tenant, SystemConfig.LOCAL_IP, null); + log.warn("[merge-delete] delete config info because no datum. dataId=" + dataId + ", groupId=" + group); + } + + } catch (Throwable e) { + log.info("[merge-error] " + dataId + ", " + group + ", " + e.toString(), e); + } + FINISHED.incrementAndGet(); + if(FINISHED.get() % 100 == 0){ + log.info("[all-merge-dump] {} / {}", FINISHED.get(), total); + } + } + log.info("[all-merge-dump] {} / {}", FINISHED.get(), total); + } + } + + /** + * 全量dump间隔 + */ + static final int DUMP_ALL_INTERVAL_IN_MINUTE = 6 * 60; + /** + * 全量dump间隔 + */ + static final int INITIAL_DELAY_IN_MINUTE = 6 * 60; + + private TaskManager dumpTaskMgr; + private TaskManager dumpAllTaskMgr; + + private static final Logger log = LoggerFactory.getLogger(DumpService.class); + + static final AtomicInteger FINISHED = new AtomicInteger(); + + static final int INIT_THREAD_COUNT = 10; + int total = 0; + private final static String TRUE_STR = "true"; + private final static String BETA_TABLE_NAME = "config_info_beta"; + private final static String TAG_TABLE_NAME = "config_info_tag"; + + Boolean isQuickStart = false; + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/DumpTask.java b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/DumpTask.java new file mode 100755 index 00000000000..e3c2453da17 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/DumpTask.java @@ -0,0 +1,444 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service.dump; + +import static com.alibaba.nacos.config.server.utils.LogUtil.defaultLog; + +import java.sql.Timestamp; +import java.util.List; + +import com.alibaba.nacos.config.server.manager.AbstractTask; +import com.alibaba.nacos.config.server.manager.TaskProcessor; +import com.alibaba.nacos.config.server.model.ConfigInfo; +import com.alibaba.nacos.config.server.model.ConfigInfo4Beta; +import com.alibaba.nacos.config.server.model.ConfigInfo4Tag; +import com.alibaba.nacos.config.server.model.Page; +import com.alibaba.nacos.config.server.service.AggrWhitelist; +import com.alibaba.nacos.config.server.service.ClientIpWhiteList; +import com.alibaba.nacos.config.server.service.ConfigService; +import com.alibaba.nacos.config.server.service.PersistService; +import com.alibaba.nacos.config.server.service.SwitchService; +import com.alibaba.nacos.config.server.service.PersistService.ConfigInfoBetaWrapper; +import com.alibaba.nacos.config.server.service.PersistService.ConfigInfoTagWrapper; +import com.alibaba.nacos.config.server.service.PersistService.ConfigInfoWrapper; +import com.alibaba.nacos.config.server.service.trace.ConfigTraceService; +import com.alibaba.nacos.config.server.utils.GroupKey2; +import com.alibaba.nacos.config.server.utils.LogUtil; +import com.alibaba.nacos.config.server.utils.MD5; +import com.alibaba.nacos.config.server.utils.StringUtils; + +/** + * Dump data task + * @author Nacos + * + */ +public class DumpTask extends AbstractTask { + + public DumpTask(String groupKey, long lastModified, String handleIp) { + this.groupKey = groupKey; + this.lastModified = lastModified; + this.handleIp = handleIp; + this.isBeta = false; + this.tag = null; + /** + * retry interval: 1s + */ + setTaskInterval(1000L); + } + + public DumpTask(String groupKey, long lastModified, String handleIp, boolean isBeta) { + this.groupKey = groupKey; + this.lastModified = lastModified; + this.handleIp = handleIp; + this.isBeta = isBeta; + this.tag = null; + /** + * retry interval: 1s + */ + setTaskInterval(1000L); + } + + public DumpTask(String groupKey, String tag, long lastModified, String handleIp, boolean isBeta) { + this.groupKey = groupKey; + this.lastModified = lastModified; + this.handleIp = handleIp; + this.isBeta = isBeta; + this.tag = tag; + /** + * retry interval: 1s + */ + setTaskInterval(1000L); + } + + @Override + public void merge(AbstractTask task) { + } + + final String groupKey; + final long lastModified; + final String handleIp; + final boolean isBeta; + final String tag; +} + +class DumpAllTask extends AbstractTask { + @Override + public void merge(AbstractTask task) { + } + + static final String TASK_ID = "dumpAllConfigTask"; +} + +class DumpAllBetaTask extends AbstractTask { + @Override + public void merge(AbstractTask task) { + } + + static final String TASK_ID = "dumpAllBetaConfigTask"; +} + +class DumpAllTagTask extends AbstractTask { + @Override + public void merge(AbstractTask task) { + } + + static final String TASK_ID = "dumpAllTagConfigTask"; +} + +class DumpChangeTask extends AbstractTask { + @Override + public void merge(AbstractTask task) { + } + + static final String TASK_ID = "dumpChangeConfigTask"; +} + + +class DumpProcessor implements TaskProcessor { + + DumpProcessor(DumpService dumpService) { + this.dumpService = dumpService; + } + + @Override + public boolean process(String taskType, AbstractTask task) { + DumpTask dumpTask = (DumpTask) task; + String[] pair = GroupKey2.parseKey(dumpTask.groupKey); + String dataId = pair[0]; + String group = pair[1]; + String tenant = pair[2]; + long lastModified = dumpTask.lastModified; + String handleIp = dumpTask.handleIp; + boolean isBeta = dumpTask.isBeta; + String tag = dumpTask.tag; + if (isBeta) { + // beta发布,则dump数据,更新beta缓存 + ConfigInfo4Beta cf = dumpService.persistService.findConfigInfo4Beta(dataId, group, tenant); + boolean result; + if (null != cf) { + result = ConfigService.dumpBeta(dataId, group, tenant, cf.getContent(), lastModified, cf.getBetaIps()); + if (result) { + ConfigTraceService.logDumpEvent(dataId, group, tenant, null, lastModified, handleIp, + ConfigTraceService.DUMP_EVENT_OK, System.currentTimeMillis() - lastModified, + cf.getContent().length()); + } + } else { + result = ConfigService.removeBeta(dataId, group, tenant); + if (result) { + ConfigTraceService.logDumpEvent(dataId, group, tenant, null, lastModified, handleIp, + ConfigTraceService.DUMP_EVENT_REMOVE_OK, System.currentTimeMillis() - lastModified, 0); + } + } + return result; + } else { + if (StringUtils.isBlank(tag)) { + ConfigInfo cf = dumpService.persistService.findConfigInfo(dataId, group, tenant); + if (dataId.equals(AggrWhitelist.AGGRIDS_METADATA)) { + if (null != cf) { + AggrWhitelist.load(cf.getContent()); + } else { + AggrWhitelist.load(null); + } + } + + if (dataId.equals(ClientIpWhiteList.CLIENT_IP_WHITELIST_METADATA)) { + if (null != cf) { + ClientIpWhiteList.load(cf.getContent()); + } else { + ClientIpWhiteList.load(null); + } + } + + if (dataId.equals(SwitchService.SWITCH_META_DATAID)) { + if (null != cf) { + SwitchService.load(cf.getContent()); + } else { + SwitchService.load(null); + } + } + + boolean result; + if (null != cf) { + result = ConfigService.dump(dataId, group, tenant, cf.getContent(), lastModified); + + if (result) { + ConfigTraceService.logDumpEvent(dataId, group, tenant, null, lastModified, handleIp, + ConfigTraceService.DUMP_EVENT_OK, System.currentTimeMillis() - lastModified, + cf.getContent().length()); + } + } else { + result = ConfigService.remove(dataId, group, tenant); + + if (result) { + ConfigTraceService.logDumpEvent(dataId, group, tenant, null, lastModified, handleIp, + ConfigTraceService.DUMP_EVENT_REMOVE_OK, System.currentTimeMillis() - lastModified, 0); + } + } + return result; + } else { + ConfigInfo4Tag cf = dumpService.persistService.findConfigInfo4Tag(dataId, group, tenant, tag); + // + boolean result; + if (null != cf) { + result = ConfigService.dumpTag(dataId, group, tenant, tag, cf.getContent(), lastModified); + if (result) { + ConfigTraceService.logDumpEvent(dataId, group, tenant, null, lastModified, handleIp, + ConfigTraceService.DUMP_EVENT_OK, System.currentTimeMillis() - lastModified, + cf.getContent().length()); + } + } else { + result = ConfigService.removeTag(dataId, group, tenant, tag); + if (result) { + ConfigTraceService.logDumpEvent(dataId, group, tenant, null, lastModified, handleIp, + ConfigTraceService.DUMP_EVENT_REMOVE_OK, System.currentTimeMillis() - lastModified, 0); + } + } + return result; + } + } + + + + } + + final DumpService dumpService; +} + + +class DumpAllProcessor implements TaskProcessor { + + DumpAllProcessor(DumpService dumpService) { + this.dumpService = dumpService; + this.persistService = dumpService.persistService; + } + + @Override + public boolean process(String taskType, AbstractTask task) { + long currentMaxId = persistService.findConfigMaxId(); + long lastMaxId = 0; + while (lastMaxId < currentMaxId) { + Page page = persistService.findAllConfigInfoFragment(lastMaxId, + PAGE_SIZE); + if (page != null && page.getPageItems() != null) { + for (PersistService.ConfigInfoWrapper cf : page.getPageItems()) { + long id = cf.getId(); + lastMaxId = id > lastMaxId ? id : lastMaxId; + if (cf.getDataId().equals(AggrWhitelist.AGGRIDS_METADATA)) { + AggrWhitelist.load(cf.getContent()); + } + + if (cf.getDataId().equals(ClientIpWhiteList.CLIENT_IP_WHITELIST_METADATA)) { + ClientIpWhiteList.load(cf.getContent()); + } + + if (cf.getDataId().equals(SwitchService.SWITCH_META_DATAID)) { + SwitchService.load(cf.getContent()); + } + + boolean result = ConfigService.dump(cf.getDataId(), cf.getGroup(), cf.getTenant(), cf.getContent(), + cf.getLastModified()); + + final String content = cf.getContent(); + final String md5 = MD5.getInstance().getMD5String(content); + LogUtil.dumpLog.info("[dump-all-ok] {}, {}, length={}, md5={}", + new Object[] { GroupKey2.getKey(cf.getDataId(), cf.getGroup()), cf.getLastModified(), + content.length(), md5 }); + } + defaultLog.info("[all-dump] {} / {}", lastMaxId, currentMaxId); + } else { + lastMaxId += PAGE_SIZE; + } + } + return true; + } + + + static final int PAGE_SIZE = 1000; + + final DumpService dumpService; + final PersistService persistService; +} + +class DumpAllBetaProcessor implements TaskProcessor { + + DumpAllBetaProcessor(DumpService dumpService) { + this.dumpService = dumpService; + this.persistService = dumpService.persistService; + } + + @Override + public boolean process(String taskType, AbstractTask task) { + int rowCount = persistService.configInfoBetaCount(); + int pageCount = (int) Math.ceil(rowCount * 1.0 / PAGE_SIZE); + + int actualRowCount = 0; + for (int pageNo = 1; pageNo <= pageCount; pageNo++) { + Page page = persistService.findAllConfigInfoBetaForDumpAll(pageNo, PAGE_SIZE); + if (page != null) { + for (ConfigInfoBetaWrapper cf : page.getPageItems()) { + boolean result = ConfigService.dumpBeta(cf.getDataId(), cf.getGroup(), cf.getTenant(), + cf.getContent(), cf.getLastModified(), cf.getBetaIps()); + LogUtil.dumpLog.info("[dump-all-beta-ok] result={}, {}, {}, length={}, md5={}", + new Object[] { result, GroupKey2.getKey(cf.getDataId(), cf.getGroup()), + cf.getLastModified(), cf.getContent().length(), cf.getMd5() }); + } + + actualRowCount += page.getPageItems().size(); + defaultLog.info("[all-dump-beta] {} / {}", actualRowCount, rowCount); + } + } + return true; + } + + + static final int PAGE_SIZE = 1000; + + final DumpService dumpService; + final PersistService persistService; +} + +class DumpAllTagProcessor implements TaskProcessor { + + DumpAllTagProcessor(DumpService dumpService) { + this.dumpService = dumpService; + this.persistService = dumpService.persistService; + } + + @Override + public boolean process(String taskType, AbstractTask task) { + int rowCount = persistService.configInfoTagCount(); + int pageCount = (int) Math.ceil(rowCount * 1.0 / PAGE_SIZE); + + int actualRowCount = 0; + for (int pageNo = 1; pageNo <= pageCount; pageNo++) { + Page page = persistService.findAllConfigInfoTagForDumpAll(pageNo, PAGE_SIZE); + if (page != null) { + for (ConfigInfoTagWrapper cf : page.getPageItems()) { + boolean result = ConfigService.dumpTag(cf.getDataId(), cf.getGroup(), cf.getTenant(), cf.getTag(), + cf.getContent(), cf.getLastModified()); + LogUtil.dumpLog.info("[dump-all-Tag-ok] result={}, {}, {}, length={}, md5={}", + new Object[] { result, GroupKey2.getKey(cf.getDataId(), cf.getGroup()), + cf.getLastModified(), cf.getContent().length(), cf.getMd5() }); + } + + actualRowCount += page.getPageItems().size(); + defaultLog.info("[all-dump-tag] {} / {}", actualRowCount, rowCount); + } + } + return true; + } + + + static final int PAGE_SIZE = 1000; + + final DumpService dumpService; + final PersistService persistService; +} + +class DumpChangeProcessor implements TaskProcessor { + + DumpChangeProcessor(DumpService dumpService, Timestamp startTime, + Timestamp endTime) { + this.dumpService = dumpService; + this.persistService = dumpService.persistService; + this.startTime = startTime; + this.endTime = endTime; + } + + @Override + public boolean process(String taskType, AbstractTask task) { + LogUtil.defaultLog.warn("quick start; startTime:{},endTime:{}", + startTime, endTime); + LogUtil.defaultLog.warn("updateMd5 start"); + long startUpdateMd5 = System.currentTimeMillis(); + List updateMd5List = persistService + .listAllGroupKeyMd5(); + LogUtil.defaultLog.warn("updateMd5 count:{}", updateMd5List.size()); + for (ConfigInfoWrapper config : updateMd5List) { + final String groupKey = GroupKey2.getKey(config.getDataId(), + config.getGroup()); + ConfigService.updateMd5(groupKey, config.getMd5(), + config.getLastModified()); + } + long endUpdateMd5 = System.currentTimeMillis(); + LogUtil.defaultLog.warn("updateMd5 done,cost:{}", endUpdateMd5 + - startUpdateMd5); + + LogUtil.defaultLog.warn("deletedConfig start"); + long startDeletedConfigTime = System.currentTimeMillis(); + List configDeleted = persistService.findDeletedConfig( + startTime, endTime); + LogUtil.defaultLog.warn("deletedConfig count:{}", configDeleted.size()); + for (ConfigInfo configInfo : configDeleted) { + if (persistService.findConfigInfo(configInfo.getDataId(), configInfo.getGroup(), + configInfo.getTenant()) == null) { + ConfigService.remove(configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant()); + } + } + long endDeletedConfigTime = System.currentTimeMillis(); + LogUtil.defaultLog.warn("deletedConfig done,cost:{}", + endDeletedConfigTime - startDeletedConfigTime); + + LogUtil.defaultLog.warn("changeConfig start"); + long startChangeConfigTime = System.currentTimeMillis(); + List changeConfigs = persistService + .findChangeConfig(startTime, endTime); + LogUtil.defaultLog.warn("changeConfig count:{}", changeConfigs.size()); + for (PersistService.ConfigInfoWrapper cf : changeConfigs) { + boolean result = ConfigService.dumpChange(cf.getDataId(), cf.getGroup(), cf.getTenant(), + cf.getContent(), cf.getLastModified()); + final String content = cf.getContent(); + final String md5 = MD5.getInstance().getMD5String(content); + LogUtil.defaultLog.info( + "[dump-change-ok] {}, {}, length={}, md5={}", + new Object[] { + GroupKey2.getKey(cf.getDataId(), cf.getGroup()), + cf.getLastModified(), content.length(), md5 }); + } + ConfigService.reloadConfig(); + long endChangeConfigTime = System.currentTimeMillis(); + LogUtil.defaultLog.warn("changeConfig done,cost:{}", + endChangeConfigTime - startChangeConfigTime); + return true; + } + + // ===================== + + final DumpService dumpService; + final PersistService persistService; + final Timestamp startTime; + final Timestamp endTime; +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/merge/MergeDataTask.java b/config/src/main/java/com/alibaba/nacos/config/server/service/merge/MergeDataTask.java new file mode 100755 index 00000000000..b211358b6f3 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/merge/MergeDataTask.java @@ -0,0 +1,68 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service.merge; + +import com.alibaba.nacos.config.server.manager.AbstractTask; + +/** + * 表示对数据进行聚合的任务。 + * + * @author jiuRen + */ +class MergeDataTask extends AbstractTask { + + MergeDataTask(String dataId, String groupId, String tenant, String clientIp) { + this(dataId, groupId, tenant, null, clientIp); + } + + MergeDataTask(String dataId, String groupId, String tenant, String tag, String clientIp) { + this.dataId = dataId; + this.groupId = groupId; + this.tenant = tenant; + this.tag = tag; + this.clientIp = clientIp; + + // 聚合延迟 + setTaskInterval(DELAY); + setLastProcessTime(System.currentTimeMillis()); + } + + @Override + public void merge(AbstractTask task) { + } + + public String getId() { + return toString(); + } + + @Override + public String toString() { + return "MergeTask[" + dataId + ", " + groupId + ", " + tenant + ", " + clientIp + "]"; + } + + public String getClientIp() { + return clientIp; + } + + + static final long DELAY = 0L; + + final String dataId; + final String groupId; + final String tenant; + final String tag; + private final String clientIp; +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/merge/MergeDatumService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/merge/MergeDatumService.java new file mode 100644 index 00000000000..0bc686ce8e3 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/merge/MergeDatumService.java @@ -0,0 +1,155 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service.merge; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.alibaba.nacos.config.server.manager.TaskManager; +import com.alibaba.nacos.config.server.model.ConfigInfo; +import com.alibaba.nacos.config.server.model.ConfigInfoAggr; +import com.alibaba.nacos.config.server.model.ConfigInfoChanged; +import com.alibaba.nacos.config.server.model.Page; +import com.alibaba.nacos.config.server.service.PersistService; +import com.alibaba.nacos.config.server.utils.ContentUtils; +import com.alibaba.nacos.config.server.utils.SystemConfig; +import com.alibaba.nacos.config.server.utils.TimeUtils; + + +/** + * 数据聚合服务。 + * + * 启动时做全量聚合 + 修改数据触发的单条聚合 + * + * @author jiuRen + */ +@Service +public class MergeDatumService { + + private PersistService persistService; + static final int INIT_THREAD_COUNT = 40; + static final AtomicInteger FINISHED = new AtomicInteger(); + static int total = 0; + + @Autowired + public MergeDatumService(PersistService persistService) { + this.persistService = persistService; + mergeTasks = new TaskManager("com.alibaba.nacos.MergeDatum"); + mergeTasks.setDefaultTaskProcessor(new MergeTaskProcessor(persistService, this)); + + } + + static List> splitList(List list, int count){ + List> result = new ArrayList>(count); + for(int i = 0 ; i < count ; i++){ + result.add(new ArrayList()); + } + for(int i = 0; i < list.size() ; i++){ + ConfigInfoChanged config = list.get(i); + result.get(i % count).add(config); + } + return result; + } + + /** + * 数据变更后调用,添加聚合任务 + */ + public void addMergeTask(String dataId, String groupId, String tenant, String tag, String clientIp) { + MergeDataTask task = new MergeDataTask(dataId, groupId, tenant, tag, clientIp); + mergeTasks.addTask(task.getId(), task); + } + + /** + * 数据变更后调用,添加聚合任务 + */ + public void addMergeTask(String dataId, String groupId, String tenant, String clientIp) { + MergeDataTask task = new MergeDataTask(dataId, groupId, tenant, clientIp); + mergeTasks.addTask(task.getId(), task); + } + + public void mergeAll() { + for (ConfigInfoChanged item : persistService.findAllAggrGroup()) { + addMergeTask(item.getDataId(), item.getGroup(), item.getTenant(), SystemConfig.LOCAL_IP); + } + } + + class MergeAllDataWorker extends Thread{ + static final int PAGE_SIZE = 10000; + + private List configInfoList; + public MergeAllDataWorker(List configInfoList) { + super("MergeAllDataWorker"); + this.configInfoList = configInfoList; + } + + @Override + public void run() { + for (ConfigInfoChanged configInfo : configInfoList) { + String dataId = configInfo.getDataId(); + String group = configInfo.getGroup(); + String tenant = configInfo.getTenant(); + try { + List datumList = new ArrayList(); + int rowCount = persistService.aggrConfigInfoCount(dataId, group, tenant); + int pageCount = (int) Math.ceil(rowCount * 1.0 / PAGE_SIZE); + for (int pageNo = 1; pageNo <= pageCount; pageNo++) { + Page page = persistService.findConfigInfoAggrByPage(dataId, group, tenant, pageNo, PAGE_SIZE); + if (page != null) { + datumList.addAll(page.getPageItems()); + log.info("[merge-query] {}, {}, size/total={}/{}", new Object[] { dataId, group, datumList.size(), rowCount }); + } + } + + final Timestamp time = TimeUtils.getCurrentTime(); + // 聚合 + if (datumList.size() > 0) { + ConfigInfo cf = MergeTaskProcessor.merge(dataId, group, tenant, datumList); + persistService.insertOrUpdate(null, null, cf, time, null, false); + log.info("[merge-ok] {}, {}, size={}, length={}, md5={}, content={}", new Object[] { dataId, group, datumList.size(), + cf.getContent().length(), cf.getMd5(), ContentUtils.truncateContent(cf.getContent()) }); + } + // 删除 + else { + persistService.removeConfigInfo(dataId, group, tenant, SystemConfig.LOCAL_IP, null); + log.warn("[merge-delete] delete config info because no datum. dataId=" + dataId + ", groupId=" + group); + } + + } catch (Exception e) { + log.info("[merge-error] " + dataId + ", " + group + ", " + e.toString(), e); + } + FINISHED.incrementAndGet(); + if(FINISHED.get() % 100 == 0){ + log.info("[all-merge-dump] {} / {}", FINISHED.get(), total); + } + } + log.info("[all-merge-dump] {} / {}", FINISHED.get(), total); + } + } + + // ===================== + + private static final Logger log = LoggerFactory.getLogger(MergeDatumService.class); + + final TaskManager mergeTasks; + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/merge/MergeTaskProcessor.java b/config/src/main/java/com/alibaba/nacos/config/server/service/merge/MergeTaskProcessor.java new file mode 100755 index 00000000000..38ac2aadae3 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/merge/MergeTaskProcessor.java @@ -0,0 +1,134 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service.merge; + +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.manager.AbstractTask; +import com.alibaba.nacos.config.server.manager.TaskProcessor; +import com.alibaba.nacos.config.server.model.ConfigInfo; +import com.alibaba.nacos.config.server.model.ConfigInfoAggr; +import com.alibaba.nacos.config.server.model.Page; +import com.alibaba.nacos.config.server.service.ConfigDataChangeEvent; +import com.alibaba.nacos.config.server.service.PersistService; +import com.alibaba.nacos.config.server.service.trace.ConfigTraceService; +import com.alibaba.nacos.config.server.utils.ContentUtils; +import com.alibaba.nacos.config.server.utils.StringUtils; +import com.alibaba.nacos.config.server.utils.SystemConfig; +import com.alibaba.nacos.config.server.utils.TimeUtils; +import com.alibaba.nacos.config.server.utils.event.EventDispatcher; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.Timestamp; +import java.util.List; +import java.util.ArrayList; + + +/** + * Merge task processor + * @author Nacos + * + */ +public class MergeTaskProcessor implements TaskProcessor { + final int PAGE_SIZE = 10000; + + MergeTaskProcessor(PersistService persistService, MergeDatumService mergeService) { + this.persistService = persistService; + this.mergeService = mergeService; + } + + @Override + public boolean process(String taskType, AbstractTask task) { + MergeDataTask mergeTask = (MergeDataTask) task; + final String dataId = mergeTask.dataId; + final String group = mergeTask.groupId; + final String tenant = mergeTask.tenant; + final String tag = mergeTask.tag; + final String clientIp = mergeTask.getClientIp(); + try { + List datumList = new ArrayList(); + int rowCount = persistService.aggrConfigInfoCount(dataId, group, tenant); + int pageCount = (int) Math.ceil(rowCount * 1.0 / PAGE_SIZE); + for (int pageNo = 1; pageNo <= pageCount; pageNo++) { + Page page = persistService.findConfigInfoAggrByPage(dataId, group, tenant, pageNo, + PAGE_SIZE); + if (page != null) { + datumList.addAll(page.getPageItems()); + log.info("[merge-query] {}, {}, size/total={}/{}", + new Object[] { dataId, group, datumList.size(), rowCount }); + } + } + + final Timestamp time = TimeUtils.getCurrentTime(); + // 聚合 + if (datumList.size() > 0) { + ConfigInfo cf = merge(dataId, group, tenant, datumList); + + persistService.insertOrUpdate(null, null, cf, time, null); + + log.info("[merge-ok] {}, {}, size={}, length={}, md5={}, content={}", new Object[] { + dataId, group, datumList.size(), cf.getContent().length(), cf.getMd5(), + ContentUtils.truncateContent(cf.getContent()) }); + + ConfigTraceService.logPersistenceEvent(dataId, group, tenant, null, time.getTime(), SystemConfig.LOCAL_IP, ConfigTraceService.PERSISTENCE_EVENT_MERGE, cf.getContent()); + } + // 删除 + else { + if (StringUtils.isBlank(tag)) { + persistService.removeConfigInfo(dataId, group, tenant, clientIp, null); + } else { + persistService.removeConfigInfoTag(dataId, group, tenant, tag, clientIp, null); + } + + log.warn("[merge-delete] delete config info because no datum. dataId=" + dataId + + ", groupId=" + group); + + ConfigTraceService.logPersistenceEvent(dataId, group, tenant, null, time.getTime(), SystemConfig.LOCAL_IP, ConfigTraceService.PERSISTENCE_EVENT_REMOVE, null); + } + + EventDispatcher.fireEvent(new ConfigDataChangeEvent(false, dataId, group, tenant, tag, time.getTime())); + + } catch (Exception e) { + mergeService.addMergeTask(dataId, group, tenant, mergeTask.getClientIp()); + log.info("[merge-error] " + dataId + ", " + group + ", " + e.toString(), e); + } + + return true; + } + + + public static ConfigInfo merge(String dataId, String group, String tenant, List datumList) { + StringBuilder sb = new StringBuilder(); + String appName = null; + for (ConfigInfoAggr aggrInfo : datumList) { + if (aggrInfo.getAppName() != null) { + appName = aggrInfo.getAppName(); + } + sb.append(aggrInfo.getContent()); + sb.append(Constants.NACOS_LINE_SEPARATOR); + } + String content = sb.substring(0, sb.lastIndexOf(Constants.NACOS_LINE_SEPARATOR)); + return new ConfigInfo(dataId, group, tenant, appName, content); + } + + // ===================== + + private static final Logger log = LoggerFactory.getLogger(MergeTaskProcessor.class); + + private PersistService persistService; + private MergeDatumService mergeService; +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/notify/AsyncNotifyService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/notify/AsyncNotifyService.java new file mode 100644 index 00000000000..3afdbc3d13e --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/notify/AsyncNotifyService.java @@ -0,0 +1,366 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service.notify; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.utils.HttpClientUtils; +import org.apache.http.concurrent.FutureCallback; +import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; +import org.apache.http.impl.nio.client.HttpAsyncClients; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.service.ConfigDataChangeEvent; +import com.alibaba.nacos.config.server.service.ServerListService; +import com.alibaba.nacos.config.server.service.trace.ConfigTraceService; +import com.alibaba.nacos.config.server.utils.LogUtil; +import com.alibaba.nacos.config.server.utils.PropertyUtil; +import com.alibaba.nacos.config.server.utils.RunningConfigUtils; +import com.alibaba.nacos.config.server.utils.StringUtils; +import com.alibaba.nacos.config.server.utils.SystemConfig; +import com.alibaba.nacos.config.server.utils.event.EventDispatcher.AbstractEventListener; +import com.alibaba.nacos.config.server.utils.event.EventDispatcher.Event; + +/** + * Async notify service + * @author Nacos + */ +@Service +public class AsyncNotifyService extends AbstractEventListener { + + @Override + public List> interest() { + List> types = new ArrayList>(); + // 触发配置变更同步通知 + types.add(ConfigDataChangeEvent.class); + return types; + } + + @Override + public void onEvent(Event event) { + + // 并发产生 ConfigDataChangeEvent + if (event instanceof ConfigDataChangeEvent) { + ConfigDataChangeEvent evt = (ConfigDataChangeEvent) event; + long dumpTs = evt.lastModifiedTs; + String dataId = evt.dataId; + String group = evt.group; + String tenant = evt.tenant; + String tag = evt.tag; + List ipList = serverListService.getServerList(); + + // 其实这里任何类型队列都可以 + Queue queue = new LinkedList(); + for (int i = 0; i < ipList.size(); i++) { + queue.add(new NotifySingleTask(dataId, group, tenant, tag, dumpTs, (String) ipList.get(i), evt.isBeta)); + } + EXCUTOR.execute(new AsyncTask(httpclient, queue)); + } + } + + @Autowired + public AsyncNotifyService(ServerListService serverListService) { + this.serverListService = serverListService; + httpclient.start(); + } + + public Executor getExecutor(){ + return EXCUTOR; + } + @SuppressWarnings("PMD.ThreadPoolCreationRule") + private static final Executor EXCUTOR = Executors.newScheduledThreadPool(100,new NotifyThreadFactory()); + + private RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(PropertyUtil.getNotifyConnectTimeout()) + .setSocketTimeout(PropertyUtil.getNotifySocketTimeout()).build(); + + private CloseableHttpAsyncClient httpclient = HttpAsyncClients.custom() + .setDefaultRequestConfig(requestConfig).build(); + + static final Logger log = LoggerFactory.getLogger(AsyncNotifyService.class); + + private ServerListService serverListService; + + class AsyncTask implements Runnable { + + + public AsyncTask(CloseableHttpAsyncClient httpclient,Queue queue) { + this.httpclient = httpclient; + this.queue = queue; + } + + @Override + public void run() { + + executeAsyncInvoke(); + + } + + private void executeAsyncInvoke() { + + while (!queue.isEmpty()) { + + NotifySingleTask task = queue.poll(); + String targetIp = task.getTargetIP(); + if (serverListService.getServerList().contains( + targetIp)) { + // 启动健康检查且有不监控的ip则直接把放到通知队列,否则通知 + if (serverListService.isHealthCheck() + && ServerListService.getServerListUnhealth().contains(targetIp)) { + // target ip 不健康,则放入通知列表中 + ConfigTraceService.logNotifyEvent(task.getDataId(), task.getGroup(), task.getTenant(), null, task.getLastModified(), + SystemConfig.LOCAL_IP, ConfigTraceService.NOTIFY_EVENT_UNHEALTH, 0, task.target); + // get delay time and set fail count to the task + int delay = getDelayTime(task); + Queue queue = new LinkedList(); + queue.add(task); + AsyncTask asyncTask = new AsyncTask(httpclient, queue); + ((ScheduledThreadPoolExecutor) EXCUTOR).schedule(asyncTask, delay, TimeUnit.MILLISECONDS); + } else { + HttpGet request = new HttpGet(task.url); + request.setHeader(NotifyService.NOTIFY_HEADER_LAST_MODIFIED, + String.valueOf(task.getLastModified())); + request.setHeader(NotifyService.NOTIFY_HEADER_OP_HANDLE_IP, SystemConfig.LOCAL_IP); + if (task.isBeta) { + request.setHeader("isBeta", "true"); + } + httpclient.execute(request, new AyscNotifyCallBack(httpclient, task)); + } + } + } + } + + private Queue queue; + private CloseableHttpAsyncClient httpclient; + + } + + + class AyscNotifyCallBack implements FutureCallback { + + public AyscNotifyCallBack(CloseableHttpAsyncClient httpclient,NotifySingleTask task + ) { + this.task = task; + this.httpclient = httpclient; + } + + @Override + public void completed(HttpResponse response) { + + long delayed = System.currentTimeMillis() - task.getLastModified(); + + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + ConfigTraceService.logNotifyEvent(task.getDataId(), + task.getGroup(), task.getTenant(), null, task.getLastModified(), + SystemConfig.LOCAL_IP, + ConfigTraceService.NOTIFY_EVENT_OK, delayed, + task.target); + } else { + log.error("[notify-error] {}, {}, to {}, result {}", + new Object[] { task.getDataId(), task.getGroup(), + task.target, + response.getStatusLine().getStatusCode() }); + ConfigTraceService.logNotifyEvent(task.getDataId(), + task.getGroup(), task.getTenant(), null, task.getLastModified(), + SystemConfig.LOCAL_IP, + ConfigTraceService.NOTIFY_EVENT_ERROR, delayed, + task.target); + + //get delay time and set fail count to the task + int delay = getDelayTime(task); + + Queue queue = new LinkedList(); + + queue.add(task); + AsyncTask asyncTask = new AsyncTask(httpclient,queue); + + ((ScheduledThreadPoolExecutor)EXCUTOR).schedule(asyncTask, delay, TimeUnit.MILLISECONDS); + + LogUtil.notifyLog.error( + "[notify-retry] target:{} dataid:{} group:{} ts:{}", + new Object[] { task.target, task.getDataId(), + task.getGroup(), task.getLastModified() }); + + } + HttpClientUtils.closeQuietly(response); + } + + @Override + public void failed(Exception ex) { + + long delayed = System.currentTimeMillis() - task.getLastModified(); + log.error("[notify-exception] " + task.getDataId() + ", " + task.getGroup() + ", to " + task.target + ", " + + ex.toString()); + log.debug("[notify-exception] " + task.getDataId() + ", " + task.getGroup() + ", to " + task.target + ", " + + ex.toString(), ex); + ConfigTraceService.logNotifyEvent(task.getDataId(), + task.getGroup(), task.getTenant(), null, task.getLastModified(), + SystemConfig.LOCAL_IP, + ConfigTraceService.NOTIFY_EVENT_EXCEPTION, delayed, + task.target); + + //get delay time and set fail count to the task + int delay = getDelayTime(task); + Queue queue = new LinkedList(); + + queue.add(task); + AsyncTask asyncTask = new AsyncTask(httpclient,queue); + + ((ScheduledThreadPoolExecutor)EXCUTOR).schedule(asyncTask, delay, TimeUnit.MILLISECONDS); + LogUtil.notifyLog.error( + "[notify-retry] target:{} dataid:{} group:{} ts:{}", + new Object[] { task.target, task.getDataId(), + task.getGroup(), task.getLastModified() }); + + } + + @Override + public void cancelled() { + + LogUtil.notifyLog.error( + "[notify-exception] target:{} dataid:{} group:{} ts:{}", + new Object[] { task.target, task.getGroup(), + task.getGroup(), task.getLastModified() }, + "CANCELED"); + + //get delay time and set fail count to the task + int delay = getDelayTime(task); + Queue queue = new LinkedList(); + + queue.add(task); + AsyncTask asyncTask = new AsyncTask(httpclient,queue); + + ((ScheduledThreadPoolExecutor)EXCUTOR).schedule(asyncTask, delay, TimeUnit.MILLISECONDS); + LogUtil.notifyLog.error( + "[notify-retry] target:{} dataid:{} group:{} ts:{}", + new Object[] { task.target, task.getDataId(), + task.getGroup(), task.getLastModified() }); + + } + private NotifySingleTask task; + private CloseableHttpAsyncClient httpclient; + } + + + static class NotifySingleTask extends NotifyTask { + + private String target; + public String url; + private boolean isBeta; + private static final String URL_PATTERN = "http://{0}{1}" + Constants.COMMUNICATION_CONTROLLER_PATH + "/dataChange" + + "?dataId={2}&group={3}"; + private static final String URL_PATTERN_TENANT = "http://{0}{1}" + Constants.COMMUNICATION_CONTROLLER_PATH + + "/dataChange" + "?dataId={2}&group={3}&tenant={4}"; + private int failCount; + + public NotifySingleTask(String dataId, String group, String tenant, long lastModified, String target) { + this(dataId, group, tenant, lastModified, target, false); + } + + public NotifySingleTask(String dataId, String group, String tenant, long lastModified, String target, + boolean isBeta) { + this(dataId, group, tenant, null, lastModified, target, isBeta); + } + + public NotifySingleTask(String dataId, String group, String tenant, String tag, long lastModified, + String target, boolean isBeta) { + super(dataId, group, tenant, lastModified); + this.target = target; + this.isBeta = isBeta; + try { + dataId = URLEncoder.encode(dataId, Constants.ENCODE); + group = URLEncoder.encode(group, Constants.ENCODE); + } catch (UnsupportedEncodingException e) { + log.error("URLEncoder encode error", e); + } + if (StringUtils.isBlank(tenant)) { + this.url = MessageFormat.format(URL_PATTERN, target, RunningConfigUtils.getContextPath(), dataId, + group); + } else { + this.url = MessageFormat.format(URL_PATTERN_TENANT, target, RunningConfigUtils.getContextPath(), dataId, + group, tenant); + } + if (StringUtils.isNotEmpty(tag)) { + url = url + "&tag=" + tag; + } + failCount = 0; + // this.executor = executor; + } + + public void setFailCount(int count){ + this.failCount = count; + } + + public int getFailCount(){ + return failCount; + } + public String getTargetIP(){ + return target; + } + + } + + + static class NotifyThreadFactory implements ThreadFactory { + + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r, + "com.alibaba.nacos.AsyncNotifyServiceThread"); + thread.setDaemon(true); + return thread; + } + } + + /** + * get delayTime and also set failCount to + * task;失败时间指数增加,以免断网场景不断重试无效任务,影响正常同步 + * @param task notify task + * @return delay + */ + private static int getDelayTime(NotifySingleTask task) { + int failCount = task.getFailCount(); + int delay = MINRETRYINTERVAL + failCount * failCount * INCREASESTEPS; + if (failCount <= MAXCOUNT) { + task.setFailCount(failCount + 1); + } + return delay; + } + + private static int MINRETRYINTERVAL = 500; + private static int INCREASESTEPS = 1000; + private static int MAXCOUNT = 6; + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/notify/NotifyService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/notify/NotifyService.java new file mode 100755 index 00000000000..439000e16a6 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/notify/NotifyService.java @@ -0,0 +1,111 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service.notify; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Iterator; +import java.util.List; + +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; + +import com.alibaba.nacos.config.server.manager.TaskManager; +import com.alibaba.nacos.config.server.service.ServerListService; + + +/** + * 通知其他节点取最新数据的服务。 监听数据变更事件,通知所有的server。 + * @author jiuRen + */ +public class NotifyService +{ + + @Autowired + public NotifyService(ServerListService serverListService) { + notifyTaskManager = new TaskManager("com.alibaba.nacos.NotifyTaskManager"); + notifyTaskManager.setDefaultTaskProcessor(new NotifyTaskProcessor(serverListService)); + } + + protected NotifyService() { + } + + /** + * 為了方便系统beta,不改变notify.do接口,新增lastModifed参数通过Http header传递 + */ + static public final String NOTIFY_HEADER_LAST_MODIFIED = "lastModified"; + static public final String NOTIFY_HEADER_OP_HANDLE_IP = "opHandleIp"; + + static public HttpResult invokeURL(String url, List headers, String encoding) throws IOException { + HttpURLConnection conn = null; + try { + conn = (HttpURLConnection) new URL(url).openConnection(); + + conn.setConnectTimeout(TIMEOUT); + conn.setReadTimeout(TIMEOUT); + conn.setRequestMethod("GET"); + + if (null != headers && !StringUtils.isEmpty(encoding)) { + for (Iterator iter = headers.iterator(); iter.hasNext();) { + conn.addRequestProperty(iter.next(), iter.next()); + } + } + conn.addRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=" + encoding); + /** + * 建立TCP连接 + */ + conn.connect(); + /** + * 这里内部发送请求 + */ + int respCode = conn.getResponseCode(); + String resp = null; + + if (HttpServletResponse.SC_OK == respCode) { + resp = IOUtils.toString(conn.getInputStream()); + } else { + resp = IOUtils.toString(conn.getErrorStream()); + } + return new HttpResult(respCode, resp); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + } + + static public class HttpResult { + final public int code; + final public String content; + + public HttpResult(int code, String content) { + this.code = code; + this.content = content; + } + } + + /** + * 和其他server的连接超时和socket超时 + */ + static final int TIMEOUT = 500; + + private TaskManager notifyTaskManager; + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/notify/NotifySingleService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/notify/NotifySingleService.java new file mode 100644 index 00000000000..6248e46a542 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/notify/NotifySingleService.java @@ -0,0 +1,161 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service.notify; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; + +import com.alibaba.nacos.config.server.manager.AbstractTask; +import com.alibaba.nacos.config.server.service.ServerListService; +import com.alibaba.nacos.config.server.utils.GroupKey2; +import com.alibaba.nacos.config.server.utils.LogUtil; + +/** + * Notify Single server + * @author Nacos + * + */ +public class NotifySingleService +{ + + static class NotifyTaskProcessorWrapper extends NotifyTaskProcessor { + + public NotifyTaskProcessorWrapper() { + /** + * serverListManager在这里没有用了 + */ + super(null); + } + + @Override + public boolean process(String taskType, AbstractTask task) { + NotifySingleTask notifyTask = (NotifySingleTask) task; + return notifyToDump(notifyTask.getDataId(), notifyTask.getGroup(), notifyTask.getTenant(), + notifyTask.getLastModified(), notifyTask.target); + } + } + + static class NotifySingleTask extends NotifyTask implements Runnable { + private static final NotifyTaskProcessorWrapper PROCESSOR = new NotifyTaskProcessorWrapper(); + private final Executor executor; + private final String target; + + private boolean isSuccess = false; + + public NotifySingleTask(String dataId, String group, String tenant, long lastModified, String target, Executor executor) { + super(dataId, group, tenant, lastModified); + this.target = target; + this.executor = executor; + } + + @Override + public void run() { + try { + this.isSuccess = PROCESSOR.process(GroupKey2.getKey(getDataId(), getGroup()), this); + } catch (Exception e) { // never goes here, but in case (运行中never中断此通知线程) + this.isSuccess = false; + LogUtil.notifyLog.error("[notify-exception] target:{} dataid:{} group:{} ts:{}", new Object[]{target, getDataId(), getGroup(), getLastModified()}); + LogUtil.notifyLog.debug("[notify-exception] target:{} dataid:{} group:{} ts:{}", + new Object[] { target, getDataId(), getGroup(), getLastModified() }, e); + } + + if (!this.isSuccess) { + LogUtil.notifyLog.error("[notify-retry] target:{} dataid:{} group:{} ts:{}", new Object[]{target, getDataId(), getGroup(), getLastModified()}); + try { + ((ScheduledThreadPoolExecutor) executor).schedule(this, 500L, TimeUnit.MILLISECONDS); + } catch (Exception e) { // 通知虽然失败,但是同时此前节点也下线了 + logger.warn("[notify-thread-pool] cluster remove node {}, current thread was tear down.", target, e); + } + } + } + } + + static class NotifyThreadFactory implements ThreadFactory { + private final String notifyTarget; + + NotifyThreadFactory(String notifyTarget) { + this.notifyTarget = notifyTarget; + } + + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r, "com.alibaba.nacos.NotifySingleServiceThread-" + notifyTarget); + thread.setDaemon(true); + return thread; + } + } + + + @Autowired + public NotifySingleService(ServerListService serverListService) { + this.serverListService = serverListService; + setupNotifyExecutors(); + } + + /** + * 系统启动时 or 集群扩容、下线时:单线程setupNotifyExecutors + * executors使用ConcurrentHashMap目的在于保证可见性 + */ + private void setupNotifyExecutors() { + List clusterIps = serverListService.getServerList(); + + for (String ip : clusterIps) { + // 固定线程数,无界队列(基于假设: 线程池的吞吐量不错,不会出现持续任务堆积,存在偶尔的瞬间压力) + @SuppressWarnings("PMD.ThreadPoolCreationRule") + Executor executor = Executors.newScheduledThreadPool(1, new NotifyThreadFactory(ip)); + + if (null == executors.putIfAbsent(ip, executor)) { + logger.warn("[notify-thread-pool] setup thread target ip {} ok.", ip); + continue; + } + } + + for (Map.Entry entry : executors.entrySet()) { + String target = entry.getKey(); + /** + * 集群节点下线 + */ + if (!clusterIps.contains(target)) { + ThreadPoolExecutor executor = (ThreadPoolExecutor) entry.getValue(); + executor.shutdown(); + executors.remove(target); + logger.warn("[notify-thread-pool] tear down thread target ip {} ok.", target); + } + } + + } + + + private final static Logger logger = LogUtil.fatalLog; + + private ServerListService serverListService; + + private ConcurrentHashMap executors = new ConcurrentHashMap(); + + public ConcurrentHashMap getExecutors() { + return executors; + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/notify/NotifyTask.java b/config/src/main/java/com/alibaba/nacos/config/server/service/notify/NotifyTask.java new file mode 100644 index 00000000000..5df454c618f --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/notify/NotifyTask.java @@ -0,0 +1,94 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service.notify; + +import com.alibaba.nacos.config.server.manager.AbstractTask; + +/** + * Notify task + * @author Nacos + * + */ +public class NotifyTask extends AbstractTask { + + private String dataId; + private String group; + private String tenant; + private long lastModified; + private int failCount; + + + public NotifyTask(String dataId, String group, String tenant, long lastModified) { + this.dataId = dataId; + this.group = group; + this.setTenant(tenant); + this.lastModified = lastModified; + setTaskInterval(3000L); + } + + + public String getDataId() { + return dataId; + } + + + public void setDataId(String dataId) { + this.dataId = dataId; + } + + + public String getGroup() { + return group; + } + + + public void setGroup(String group) { + this.group = group; + } + + + public int getFailCount() { + return failCount; + } + + + public void setFailCount(int failCount) { + this.failCount = failCount; + } + + public long getLastModified() { + return lastModified; + } + + public void setLastModified(long lastModified) { + this.lastModified = lastModified; + } + + @Override + public void merge(AbstractTask task) { + // 进行merge, 但什么都不做, 相同的dataId和group的任务,后来的会代替之前的 + + } + + public String getTenant() { + return tenant; + } + + public void setTenant(String tenant) { + this.tenant = tenant; + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/notify/NotifyTaskProcessor.java b/config/src/main/java/com/alibaba/nacos/config/server/service/notify/NotifyTaskProcessor.java new file mode 100755 index 00000000000..722ca42d55c --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/notify/NotifyTaskProcessor.java @@ -0,0 +1,102 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service.notify; + +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.List; + +import org.apache.http.HttpStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.manager.AbstractTask; +import com.alibaba.nacos.config.server.manager.TaskProcessor; +import com.alibaba.nacos.config.server.service.ServerListService; +import com.alibaba.nacos.config.server.service.notify.NotifyService.HttpResult; +import com.alibaba.nacos.config.server.service.trace.ConfigTraceService; +import com.alibaba.nacos.config.server.utils.RunningConfigUtils; +import com.alibaba.nacos.config.server.utils.SystemConfig; + + +/** + * 通知服务。数据库变更后,通知所有server,包括自己,加载新数据。 + * @author Nacos + */ +public class NotifyTaskProcessor implements TaskProcessor { + + public NotifyTaskProcessor(ServerListService serverListService) { + this.serverListService = serverListService; + } + + @Override + public boolean process(String taskType, AbstractTask task) { + NotifyTask notifyTask = (NotifyTask) task; + String dataId = notifyTask.getDataId(); + String group = notifyTask.getGroup(); + String tenant = notifyTask.getTenant(); + long lastModified = notifyTask.getLastModified(); + + boolean isok = true; + + for (String ip : serverListService.getServerList()) { + isok = notifyToDump(dataId, group, tenant,lastModified, ip) && isok; + } + return isok; + } + + /** + * 通知其他server + */ + boolean notifyToDump(String dataId, String group, String tenant, long lastModified, String serverIp) { + long delayed = System.currentTimeMillis() - lastModified; + try { + // XXX 為了方便系统beta,不改变notify.do接口,新增lastModifed参数通过Http header传递 + List headers = Arrays.asList( + NotifyService.NOTIFY_HEADER_LAST_MODIFIED, String.valueOf(lastModified), + NotifyService.NOTIFY_HEADER_OP_HANDLE_IP, SystemConfig.LOCAL_IP); + String urlString = MessageFormat.format(URL_PATTERN, serverIp, RunningConfigUtils.getContextPath(), dataId, + group); + + HttpResult result = NotifyService.invokeURL(urlString, headers, Constants.ENCODE); + if (result.code == HttpStatus.SC_OK) { + ConfigTraceService.logNotifyEvent(dataId, group, tenant, null, lastModified, SystemConfig.LOCAL_IP, ConfigTraceService.NOTIFY_EVENT_OK, delayed, serverIp); + return true; + } else { + log.error("[notify-error] {}, {}, to {}, result {}", new Object[] { dataId, group, + serverIp, result.code }); + ConfigTraceService.logNotifyEvent(dataId, group, tenant, null, lastModified, SystemConfig.LOCAL_IP, ConfigTraceService.NOTIFY_EVENT_ERROR, delayed, serverIp); + return false; + } + } catch (Exception e) { + log.error( + "[notify-exception] " + dataId + ", " + group + ", to " + serverIp + ", " + + e.toString()); + log.debug("[notify-exception] " + dataId + ", " + group + ", to " + serverIp + ", " + e.toString(), e); + ConfigTraceService.logNotifyEvent(dataId, group, tenant, null, lastModified, SystemConfig.LOCAL_IP, ConfigTraceService.NOTIFY_EVENT_EXCEPTION, delayed, serverIp); + return false; + } + } + + + static final Logger log = LoggerFactory.getLogger(NotifyTaskProcessor.class); + + static final String URL_PATTERN = "http://{0}{1}" + Constants.COMMUNICATION_CONTROLLER_PATH + "/dataChange" + + "?dataId={2}&group={3}"; + + final ServerListService serverListService; +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/trace/ConfigTraceService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/trace/ConfigTraceService.java new file mode 100644 index 00000000000..ae027185128 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/trace/ConfigTraceService.java @@ -0,0 +1,109 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service.trace; + +import com.alibaba.nacos.config.server.utils.LogUtil; +import com.alibaba.nacos.config.server.utils.MD5; +import com.alibaba.nacos.config.server.utils.SystemConfig; + +import org.apache.commons.lang.StringUtils; +import org.springframework.stereotype.Service; +/** + * Config trace + * @author Nacos + * + */ +@Service +public class ConfigTraceService { + public static final String PERSISTENCE_EVENT_PUB = "pub"; + public static final String PERSISTENCE_EVENT_REMOVE = "remove"; + public static final String PERSISTENCE_EVENT_MERGE = "merge"; + + public static final String NOTIFY_EVENT_OK = "ok"; + public static final String NOTIFY_EVENT_ERROR = "error"; + public static final String NOTIFY_EVENT_UNHEALTH = "unhealth"; + public static final String NOTIFY_EVENT_EXCEPTION = "exception"; + + public static final String DUMP_EVENT_OK = "ok"; + public static final String DUMP_EVENT_REMOVE_OK = "remove-ok"; + public static final String DUMP_EVENT_ERROR = "error"; + + public static final String PULL_EVENT_OK = "ok"; + public static final String PULL_EVENT_NOTFOUND = "not-found"; + public static final String PULL_EVENT_CONFLICT = "conflict"; + public static final String PULL_EVENT_ERROR = "error"; + + public static void logPersistenceEvent(String dataId, String group, String tenant, String requestIpAppName, long ts, String handleIp, String type , String content) { + if (!LogUtil.traceLog.isInfoEnabled()) { + return; + } + // 方便tlog切分 + if (StringUtils.isBlank(tenant)) { + tenant = null; + } + //localIp | dataid | group | tenant | requestIpAppName | ts | handleIp | event | type | [delayed = -1] | ext(md5) + String md5 = content == null ? null : MD5.getInstance().getMD5String(content); + LogUtil.traceLog.info("{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}", new Object[]{SystemConfig.LOCAL_IP, dataId, group, tenant, requestIpAppName, ts, handleIp, "persist", type, -1, md5}); + } + + public static void logNotifyEvent(String dataId, String group, String tenant, String requestIpAppName, long ts, String handleIp, String type,long delayed, String targetIp) { + if (!LogUtil.traceLog.isInfoEnabled()) { + return; + } + // 方便tlog切分 + if (StringUtils.isBlank(tenant)) { + tenant = null; + } + //localIp | dataid | group | tenant | requestIpAppName | ts | handleIp | event | type | [delayed] | ext(targetIp) + LogUtil.traceLog.info("{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}", new Object[]{SystemConfig.LOCAL_IP, dataId, group, tenant, requestIpAppName, ts, handleIp, "notify", type, delayed, targetIp}); + } + + public static void logDumpEvent(String dataId, String group, String tenant, String requestIpAppName, long ts, String handleIp, String type, long delayed, long length) { + if (!LogUtil.traceLog.isInfoEnabled()) { + return; + } + // 方便tlog切分 + if (StringUtils.isBlank(tenant)) { + tenant = null; + } + //localIp | dataid | group | tenant | requestIpAppName | ts | handleIp | event | type | [delayed] | length + LogUtil.traceLog.info("{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}", new Object[]{SystemConfig.LOCAL_IP, dataId, group, tenant, requestIpAppName, ts, handleIp, "dump", type, delayed, length}); + } + + public static void logDumpAllEvent(String dataId, String group, String tenant, String requestIpAppName, long ts, String handleIp, String type) { + if (!LogUtil.traceLog.isInfoEnabled()) { + return; + } + // 方便tlog切分 + if (StringUtils.isBlank(tenant)) { + tenant = null; + } + //localIp | dataid | group | tenant | requestIpAppName | ts | handleIp | event | type | [delayed = -1] + LogUtil.traceLog.info("{}|{}|{}|{}|{}|{}|{}|{}|{}|{}", new Object[]{SystemConfig.LOCAL_IP, dataId, group, tenant, requestIpAppName, ts, handleIp, "dump-all", type, -1}); + } + + public static void logPullEvent(String dataId, String group, String tenant, String requestIpAppName, long ts, String type, long delayed, String clientIp) { + if (!LogUtil.traceLog.isInfoEnabled()) { + return; + } + // 方便tlog切分 + if (StringUtils.isBlank(tenant)) { + tenant = null; + } + //localIp | dataid | group | tenant| requestIpAppName| ts | event | type | [delayed] | ext(clientIp) + LogUtil.traceLog.info("{}|{}|{}|{}|{}|{}|{}|{}|{}|{}", new Object[]{SystemConfig.LOCAL_IP, dataId, group, tenant, requestIpAppName, ts, "pull", type, delayed, clientIp}); + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/AccumulateStatCount.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/AccumulateStatCount.java new file mode 100644 index 00000000000..813e1f7a3d8 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/AccumulateStatCount.java @@ -0,0 +1,41 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +import java.util.concurrent.atomic.AtomicLong; + + +/** + * Accumulate Stat Count + * @author Nacos + * + */ +public class AccumulateStatCount { + + final AtomicLong total = new AtomicLong(0); + long lastStatValue = 0; + + + public long increase() { + return total.incrementAndGet(); + } + + public long stat() { + long tmp = total.get() - lastStatValue; + lastStatValue += tmp; + return tmp; + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/AppNameUtils.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/AppNameUtils.java new file mode 100644 index 00000000000..53a53faa89c --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/AppNameUtils.java @@ -0,0 +1,97 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +import java.io.File; + +/** + * App util + * + * @author Nacos + * + */ +public class AppNameUtils { + + private static final String PARAM_MARKING_PROJECT = "project.name"; + private static final String PARAM_MARKING_JBOSS = "jboss.server.home.dir"; + private static final String PARAM_MARKING_JETTY = "jetty.home"; + private static final String PARAM_MARKING_TOMCAT = "catalina.base"; + + private static final String LINUX_ADMIN_HOME = "/home/admin/"; + private static final String SERVER_JBOSS = "jboss"; + private static final String SERVER_JETTY = "jetty"; + private static final String SERVER_TOMCAT = "tomcat"; + private static final String SERVER_UNKNOWN = "unknown server"; + + public static String getAppName() { + String appName = null; + + appName = getAppNameByProjectName(); + if (appName != null) { + return appName; + } + + appName = getAppNameByServerHome(); + if (appName != null) { + return appName; + } + + return "unknown"; + } + + + private static String getAppNameByProjectName() { + return System.getProperty(PARAM_MARKING_PROJECT); + } + + + private static String getAppNameByServerHome() { + String serverHome = null; + if (SERVER_JBOSS.equals(getServerType())) { + serverHome = System.getProperty(PARAM_MARKING_JBOSS); + } + else if (SERVER_JETTY.equals(getServerType())) { + serverHome = System.getProperty(PARAM_MARKING_JETTY); + } + else if (SERVER_TOMCAT.equals(getServerType())) { + serverHome = System.getProperty(PARAM_MARKING_TOMCAT); + } + + if (serverHome != null && serverHome.startsWith(LINUX_ADMIN_HOME)) { + return StringUtils.substringBetween(serverHome, LINUX_ADMIN_HOME, File.separator); + } + + return null; + } + + private static String getServerType() { + String serverType = null; + if (System.getProperty(PARAM_MARKING_JBOSS) != null) { + serverType = SERVER_JBOSS; + } + else if (System.getProperty(PARAM_MARKING_JETTY) != null) { + serverType = SERVER_JETTY; + } + else if (System.getProperty(PARAM_MARKING_TOMCAT) != null) { + serverType = SERVER_TOMCAT; + } + else { + serverType = SERVER_UNKNOWN; + } + return serverType; + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/ContentUtils.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/ContentUtils.java new file mode 100644 index 00000000000..45154a0a0f1 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/ContentUtils.java @@ -0,0 +1,75 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +import static com.alibaba.nacos.config.server.constant.Constants.WORD_SEPARATOR; + +import com.alibaba.nacos.config.server.constant.Constants; + +/** + * Content utils + * @author Nacos + * + */ +public class ContentUtils { + + public static void verifyIncrementPubContent(String content) { + + if (content == null || content.length() == 0) { + throw new IllegalArgumentException("发布/删除内容不能为空"); + } + for (int i = 0; i < content.length(); i++) { + char c = content.charAt(i); + if (c == '\r' || c == '\n') { + throw new IllegalArgumentException("发布/删除内容不能包含回车和换行"); + } + if (c == Constants.WORD_SEPARATOR.charAt(0)) { + throw new IllegalArgumentException("发布/删除内容不能包含(char)2"); + } + } + } + + + public static String getContentIdentity(String content) { + int index = content.indexOf(WORD_SEPARATOR); + if (index == -1) { + throw new IllegalArgumentException("内容没有包含分隔符"); + } + return content.substring(0, index); + } + + + public static String getContent(String content) { + int index = content.indexOf(WORD_SEPARATOR); + if (index == -1) { + throw new IllegalArgumentException("内容没有包含分隔符"); + } + return content.substring(index + 1); + } + + public static String truncateContent(String content) { + if (content == null) { + return ""; + } + else if (content.length() <= LIMIT_CONTENT_SIZE) { + return content; + } + else { + return content.substring(0, 100) + "..."; + } + } + private final static int LIMIT_CONTENT_SIZE = 100; +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/GroupKey.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/GroupKey.java new file mode 100644 index 00000000000..0081e2ba44d --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/GroupKey.java @@ -0,0 +1,120 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +/** + * 合成dataId+groupId的形式。对dataId和groupId中的保留字符做转义。 + * + * @author jiuRen + */ +public class GroupKey { + + static public String getKey(String dataId, String group) { + StringBuilder sb = new StringBuilder(); + urlEncode(dataId, sb); + sb.append('+'); + urlEncode(group, sb); + return sb.toString(); + } + + static public String getKeyTenant(String dataId, String group, String tenant) { + StringBuilder sb = new StringBuilder(); + urlEncode(dataId, sb); + sb.append('+'); + urlEncode(group, sb); + if (StringUtils.isNotEmpty(tenant)) { + sb.append('+'); + urlEncode(tenant, sb); + } + return sb.toString(); + } + + static public String getKey(String dataId, String group, String datumStr) { + StringBuilder sb = new StringBuilder(); + urlEncode(dataId, sb); + sb.append('+'); + urlEncode(group, sb); + sb.append('+'); + urlEncode(datumStr, sb); + return sb.toString(); + } + + static public String[] parseKey(String groupKey) { + StringBuilder sb = new StringBuilder(); + String dataId = null; + String group = null; + String tenant = null; + + for (int i = 0; i < groupKey.length(); ++i) { + char c = groupKey.charAt(i); + if ('+' == c) { + if (null == dataId) { + dataId = sb.toString(); + sb.setLength(0); + } else if (null == group) { + group = sb.toString(); + sb.setLength(0); + } else { + throw new IllegalArgumentException("invalid groupkey:" + groupKey); + } + } else if ('%' == c) { + char next = groupKey.charAt(++i); + char nextnext = groupKey.charAt(++i); + if ('2' == next && 'B' == nextnext) { + sb.append('+'); + } else if ('2' == next && '5' == nextnext) { + sb.append('%'); + } else { + throw new IllegalArgumentException("invalid groupkey:" + groupKey); + } + } else { + sb.append(c); + } + } + + if (StringUtils.isBlank(group)) { + group = sb.toString(); + if (group.length() == 0) { + throw new IllegalArgumentException("invalid groupkey:" + groupKey); + } + } else { + tenant = sb.toString(); + if (group.length() == 0) { + throw new IllegalArgumentException("invalid groupkey:" + groupKey); + } + } + + return new String[] { dataId, group, tenant }; + } + + /** + * + -> %2B + * % -> %25 + */ + static void urlEncode(String str, StringBuilder sb) { + for (int idx = 0; idx < str.length(); ++idx) { + char c = str.charAt(idx); + if ('+' == c) { + sb.append("%2B"); + } else if ('%' == c) { + sb.append("%25"); + } else { + sb.append(c); + } + } + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/GroupKey2.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/GroupKey2.java new file mode 100644 index 00000000000..4736dbe4aac --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/GroupKey2.java @@ -0,0 +1,109 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; +/** + * Group key util + * @author Nacos + * + */ +public class GroupKey2 { + + static public String getKey(String dataId, String group) { + StringBuilder sb = new StringBuilder(); + urlEncode(dataId, sb); + sb.append('+'); + urlEncode(group, sb); + return sb.toString(); + } + + static public String getKey(String dataId, String group, String tenant) { + StringBuilder sb = new StringBuilder(); + urlEncode(dataId, sb); + sb.append('+'); + urlEncode(group, sb); + if (StringUtils.isNotEmpty(tenant)) { + sb.append('+'); + urlEncode(tenant, sb); + } + return sb.toString(); + } + + static public String[] parseKey(String groupKey) { + StringBuilder sb = new StringBuilder(); + String dataId = null; + String group = null; + String tenant = null; + + for (int i = 0; i < groupKey.length(); ++i) { + char c = groupKey.charAt(i); + if ('+' == c) { + if (null == dataId) { + dataId = sb.toString(); + sb.setLength(0); + } else if (null == group) { + group = sb.toString(); + sb.setLength(0); + } else { + throw new IllegalArgumentException("invalid groupkey:" + groupKey); + } + } else if ('%' == c) { + char next = groupKey.charAt(++i); + char nextnext = groupKey.charAt(++i); + if ('2' == next && 'B' == nextnext) { + sb.append('+'); + } else if ('2' == next && '5' == nextnext) { + sb.append('%'); + } else { + throw new IllegalArgumentException("invalid groupkey:" + groupKey); + } + } else { + sb.append(c); + } + } + + if (StringUtils.isBlank(group)) { + group = sb.toString(); + if (group.length() == 0) { + throw new IllegalArgumentException("invalid groupkey:" + groupKey); + } + } else { + tenant = sb.toString(); + if (group.length() == 0) { + throw new IllegalArgumentException("invalid groupkey:" + groupKey); + } + } + + return new String[] { dataId, group, tenant }; + } + + /** + * + -> %2B + * % -> %25 + */ + static void urlEncode(String str, StringBuilder sb) { + for (int idx = 0; idx < str.length(); ++idx) { + char c = str.charAt(idx); + if ('+' == c) { + sb.append("%2B"); + } else if ('%' == c) { + sb.append("%25"); + } else { + sb.append(c); + } + } + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/IPUtil.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/IPUtil.java new file mode 100644 index 00000000000..f266f83b7a7 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/IPUtil.java @@ -0,0 +1,55 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +/** + * ip util + * @author Nacos + * + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public class IPUtil { + + public static boolean isIPV4(String addr) { + if (null == addr) { + return false; + } + String rexp = "^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)$"; + + Pattern pat = Pattern.compile(rexp); + + Matcher mat = pat.matcher(addr); + + boolean ipAddress = mat.find(); + return ipAddress; + } + + public static boolean isIPV6(String addr) { + if (null == addr) { + return false; + } + String rexp = "^([\\da-fA-F]{1,4}:){7}[\\da-fA-F]{1,4}$"; + + Pattern pat = Pattern.compile(rexp); + + Matcher mat = pat.matcher(addr); + + boolean ipAddress = mat.find(); + return ipAddress; + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/JSONUtils.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/JSONUtils.java new file mode 100644 index 00000000000..db6b5c21767 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/JSONUtils.java @@ -0,0 +1,60 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +import java.io.IOException; + +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.DeserializationConfig.Feature; +import org.codehaus.jackson.type.JavaType; +import org.codehaus.jackson.type.TypeReference; + +/** + * json util + * @author Nacos + * + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public class JSONUtils { + + static ObjectMapper mapper = new ObjectMapper(); + + static { + mapper.disable(Feature.FAIL_ON_UNKNOWN_PROPERTIES); + } + + public static String serializeObject(Object o) throws IOException { + return mapper.writeValueAsString(o); + } + + public static Object deserializeObject(String s, Class clazz) throws IOException { + return mapper.readValue(s, clazz); + } + + public static Object deserializeObject(String s, TypeReference typeReference) + throws IOException { + return mapper.readValue(s, typeReference); + } + + public static JavaType getCollectionType(Class collectionClass, Class... elementClasses) { + return mapper.getTypeFactory().constructParametricType(collectionClass, elementClasses); + } + + public static Object deserializeCollection(String s, JavaType type) throws IOException { + return mapper.readValue(s, type); + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/LogUtil.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/LogUtil.java new file mode 100755 index 00000000000..c9072505ce6 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/LogUtil.java @@ -0,0 +1,100 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.joran.JoranConfigurator; +import ch.qos.logback.core.joran.spi.JoranException; + +/** + * log util + * @author Nacos + * + */ +public class LogUtil { + + static { + LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); + lc.reset(); + + JoranConfigurator configurator = new JoranConfigurator(); + + String nacosDir = System.getProperty("nacos.home"); + if (StringUtils.isBlank(nacosDir)) { + configurator.setContext(lc); + try { + configurator.doConfigure(LogUtil.class.getResource("/nacos-config-logback.xml")); + } catch (JoranException e) { + System.err.println("init logger fail by nacos-config-logback.xml"); + } + } else { + configurator.setContext(lc); + try { + configurator.doConfigure(nacosDir + "/conf/nacos-logback.xml"); + } catch (JoranException e) { + System.err.println("init logger fail by " + nacosDir + "/conf/nacos-logback.xml"); + } + } + } + + /** + * 默认的日志 + */ + static public final Logger defaultLog = LoggerFactory.getLogger("com.alibaba.nacos.config.startLog"); + + /** + * 致命错误,需要告警 + */ + static public final Logger fatalLog = LoggerFactory + .getLogger("com.alibaba.nacos.config.fatal"); + + /** + * 客户端GET方法获取数据的日志 + */ + static public final Logger pullLog = LoggerFactory + .getLogger("com.alibaba.nacos.config.pullLog"); + + static public final Logger pullCheckLog = LoggerFactory + .getLogger("com.alibaba.nacos.config.pullCheckLog"); + /** + * 从DB dump数据的日志 + */ + static public final Logger dumpLog = LoggerFactory + .getLogger("com.alibaba.nacos.config.dumpLog"); + + static public final Logger memoryLog = LoggerFactory + .getLogger("com.alibaba.nacos.config.monitorLog"); + + static public final Logger clientLog = LoggerFactory + .getLogger("com.alibaba.nacos.config.clientLog"); + + static public final Logger sdkLog = LoggerFactory + .getLogger("com.alibaba.nacos.config.sdkLog"); + + static public final Logger traceLog = LoggerFactory + .getLogger("com.alibaba.nacos.config.traceLog"); + + static public final Logger aclLog = LoggerFactory + .getLogger("com.alibaba.nacos.config.aclLog"); + + static public final Logger notifyLog = LoggerFactory + .getLogger("com.alibaba.nacos.config.notifyLog"); + + static public final Logger appCollectorLog = LoggerFactory + .getLogger("com.alibaba.nacos.config.appCollectorLog"); +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/MD5.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/MD5.java new file mode 100644 index 00000000000..8e41b322171 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/MD5.java @@ -0,0 +1,168 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.locks.ReentrantLock; + +import com.alibaba.nacos.config.server.constant.Constants; + +/** + * md5 + * @author Nacos + * + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public class MD5 { + private static char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + private final static int DIGITS_COUNT = 16; + private final static int DIGITS_CHAR_SIZE = 32; + + private static Map rDigits = new HashMap(16); + static { + for (int i = 0; i < digits.length; ++i) { + rDigits.put(digits[i], i); + } + } + + private static MD5 me = new MD5(); + private MessageDigest mHasher; + private ReentrantLock opLock = new ReentrantLock(); + + + private MD5() { + try { + mHasher = MessageDigest.getInstance("md5"); + } + catch (Exception e) { + throw new RuntimeException(e); + } + + } + + + public static MD5 getInstance() { + return me; + } + + + public String getMD5String(String content) { + return bytes2string(hash(content)); + } + + + public String getMD5String(byte[] content) { + return bytes2string(hash(content)); + } + + + public byte[] getMD5Bytes(byte[] content) { + return hash(content); + } + + + /** + * 对字符串进行md5 + * + * @param str + * @return md5 byte[16] + */ + public byte[] hash(String str) { + opLock.lock(); + try { + byte[] bt = mHasher.digest(str.getBytes(Constants.ENCODE)); + if (null == bt || bt.length != DIGITS_COUNT) { + throw new IllegalArgumentException("md5 need"); + } + return bt; + } + catch (UnsupportedEncodingException e) { + throw new RuntimeException("unsupported utf-8 encoding", e); + } + finally { + opLock.unlock(); + } + } + + + /** + * 对二进制数据进行md5 + * + * @param str + * @return md5 byte[16] + */ + public byte[] hash(byte[] data) { + opLock.lock(); + try { + byte[] bt = mHasher.digest(data); + if (null == bt || bt.length != DIGITS_COUNT) { + throw new IllegalArgumentException("md5 need"); + } + return bt; + } + finally { + opLock.unlock(); + } + } + + + /** + * 将一个字节数组转化为可见的字符串 + * + * @param bt + * @return + */ + public String bytes2string(byte[] bt) { + int l = bt.length; + + char[] out = new char[l << 1]; + + for (int i = 0, j = 0; i < l; i++) { + out[j++] = digits[(0xF0 & bt[i]) >>> 4]; + out[j++] = digits[0x0F & bt[i]]; + } + + return new String(out); + } + + + /** + * 将字符串转换为bytes + * + * @param str + * @return byte[] + */ + public byte[] string2bytes(String str) { + if (null == str) { + throw new NullPointerException("参数不能为空"); + } + if (str.length() != DIGITS_CHAR_SIZE) { + throw new IllegalArgumentException("字符串长度必须是32"); + } + byte[] data = new byte[16]; + char[] chs = str.toCharArray(); + for (int i = 0; i < DIGITS_COUNT; ++i) { + int h = rDigits.get(chs[i * 2]).intValue(); + int l = rDigits.get(chs[i * 2 + 1]).intValue(); + data[i] = (byte) ((h & 0x0F) << 4 | (l & 0x0F)); + } + return data; + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/MD5Util.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/MD5Util.java new file mode 100755 index 00000000000..8de5c95221f --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/MD5Util.java @@ -0,0 +1,183 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +import static com.alibaba.nacos.config.server.constant.Constants.LINE_SEPARATOR; +import static com.alibaba.nacos.config.server.constant.Constants.WORD_SEPARATOR; + +import java.io.CharArrayWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.Writer; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang.StringUtils; + +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.service.ConfigService; + +/** + * 轮询逻辑封装类 + * @author Nacos + * + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public class MD5Util { + + static public List compareMd5(HttpServletRequest request, + HttpServletResponse response, Map clientMd5Map) { + List changedGroupKeys = new ArrayList(); + String tag = request.getHeader("Vipserver-Tag"); + for (Map.Entry entry : clientMd5Map.entrySet()) { + String groupKey = entry.getKey(); + String clientMd5 = entry.getValue(); + String ip = RequestUtil.getRemoteIp(request); + boolean isUptodate = ConfigService.isUptodate(groupKey, clientMd5, ip, tag); + if (!isUptodate) { + changedGroupKeys.add(groupKey); + } + } + return changedGroupKeys; + } + + static public String compareMd5OldResult(List changedGroupKeys) { + StringBuilder sb = new StringBuilder(); + + for (String groupKey : changedGroupKeys) { + String[] dataIdGroupId = GroupKey2.parseKey(groupKey); + sb.append(dataIdGroupId[0]); + sb.append(":"); + sb.append(dataIdGroupId[1]); + sb.append(";"); + } + return sb.toString(); + } + + static public String compareMd5ResultString(List changedGroupKeys) throws IOException { + if (null == changedGroupKeys) { + return ""; + } + + StringBuilder sb = new StringBuilder(); + + for (String groupKey : changedGroupKeys) { + String[] dataIdGroupId = GroupKey2.parseKey(groupKey); + sb.append(dataIdGroupId[0]); + sb.append(WORD_SEPARATOR); + sb.append(dataIdGroupId[1]); + // if have tenant, then set it + if (dataIdGroupId.length == 3) { + if (StringUtils.isNotBlank(dataIdGroupId[2])) { + sb.append(WORD_SEPARATOR); + sb.append(dataIdGroupId[2]); + } + } + sb.append(LINE_SEPARATOR); + } + + // 对WORD_SEPARATOR和LINE_SEPARATOR不可见字符进行编码, 编码后的值为%02和%01 + return URLEncoder.encode(sb.toString(), "UTF-8"); + } + + /** + * 解析传输协议 + * 传输协议有两种格式(w为字段分隔符,l为每条数据分隔符): + * 老报文:D w G w MD5 l + * 新报文:D w G w MD5 w T l + * @param configKeysString 协议字符串 + * @return 协议报文 + */ + static public Map getClientMd5Map(String configKeysString) { + + Map md5Map = new HashMap(5); + + if (null == configKeysString || "".equals(configKeysString)) { + return md5Map; + } + int start = 0; + List tmpList = new ArrayList(3); + for (int i = start; i < configKeysString.length(); i++) { + char c = configKeysString.charAt(i); + if (c == WORD_SEPARATOR_CHAR) { + tmpList.add(configKeysString.substring(start, i)); + start = i + 1; + if (tmpList.size() > 3) { + // 畸形报文。返回参数错误 + throw new IllegalArgumentException("invalid protocol,too much key"); + } + } else if (c == LINE_SEPARATOR_CHAR) { + String endValue = ""; + if (start + 1 <= i) { + endValue = configKeysString.substring(start, i); + } + start = i + 1; + + // 如果老的报文,最后一位是md5。多租户后报文为tenant。 + if (tmpList.size() == 2) { + String groupKey = GroupKey2.getKey(tmpList.get(0), tmpList.get(1)); + groupKey = SingletonRepository.DataIdGroupIdCache.getSingleton(groupKey); + md5Map.put(groupKey, endValue); + } else { + String groupKey = GroupKey2.getKey(tmpList.get(0), tmpList.get(1), endValue); + groupKey = SingletonRepository.DataIdGroupIdCache.getSingleton(groupKey); + md5Map.put(groupKey, tmpList.get(2)); + } + tmpList.clear(); + + // 对畸形报文进行保护 + if (md5Map.size() > 10000) { + throw new IllegalArgumentException("invalid protocol, too much listener"); + } + } + } + return md5Map; + } + + static public String toString(InputStream input, String encoding) throws IOException { + return (null == encoding) ? toString(new InputStreamReader(input, Constants.ENCODE)) + : toString(new InputStreamReader(input, encoding)); + } + + static public String toString(Reader reader) throws IOException { + CharArrayWriter sw = new CharArrayWriter(); + copy(reader, sw); + return sw.toString(); + } + + static public long copy(Reader input, Writer output) throws IOException { + char[] buffer = new char[1024]; + long count = 0; + for (int n = 0; (n = input.read(buffer)) >= 0;) { + output.write(buffer, 0, n); + count += n; + } + return count; + } + + static final char WORD_SEPARATOR_CHAR = (char) 2; + static final char LINE_SEPARATOR_CHAR = (char) 1; + +} + diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/PaginationHelper.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/PaginationHelper.java new file mode 100644 index 00000000000..63b1a377afa --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/PaginationHelper.java @@ -0,0 +1,215 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +import java.util.List; + +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; + +import com.alibaba.nacos.config.server.model.Page; + + +/** + * 分页辅助类 + * + * @author boyan + * @date 2010-5-6 + * @param + */ + +public class PaginationHelper { + + /** + * 取分页 + * + * @param jt + * jdbcTemplate + * @param sqlCountRows + * 查询总数的SQL + * @param sqlFetchRows + * 查询数据的sql + * @param args + * 查询参数 + * @param pageNo + * 页数 + * @param pageSize + * 每页大小 + * @param rowMapper + * @return + */ + public Page fetchPage(final JdbcTemplate jt, final String sqlCountRows, final String sqlFetchRows, + final Object args[], final int pageNo, final int pageSize, final RowMapper rowMapper) { + return fetchPage(jt, sqlCountRows, sqlFetchRows, args, pageNo, pageSize, null, rowMapper); + } + + public Page fetchPage(final JdbcTemplate jt, final String sqlCountRows, final String sqlFetchRows, + final Object args[], final int pageNo, final int pageSize, final Long lastMaxId, + final RowMapper rowMapper) { + if (pageNo <= 0 || pageSize <= 0) { + throw new IllegalArgumentException("pageNo and pageSize must be greater than zero"); + } + + // 查询当前记录总数 + Integer rowCountInt = jt.queryForObject(sqlCountRows, Integer.class, args); + if (rowCountInt == null) { + throw new IllegalArgumentException("fetchPageLimit error"); + } + final int rowCount = rowCountInt.intValue(); + + // 计算页数 + int pageCount = rowCount / pageSize; + if (rowCount > pageSize * pageCount) { + pageCount++; + } + + // 创建Page对象 + final Page page = new Page(); + page.setPageNumber(pageNo); + page.setPagesAvailable(pageCount); + page.setTotalCount(rowCount); + + if (pageNo > pageCount) { + return null; + } + + final int startRow = (pageNo - 1) * pageSize; + String selectSQL = ""; + if (PropertyUtil.isStandaloneMode()) { + selectSQL = sqlFetchRows + " OFFSET "+startRow+" ROWS FETCH NEXT "+pageSize+" ROWS ONLY"; + } else if (lastMaxId != null) { + selectSQL = sqlFetchRows + " and id > " + lastMaxId + " order by id asc" + " limit " + 0 + "," + pageSize; + } else { + selectSQL = sqlFetchRows + " limit " + startRow + "," + pageSize; + } + + List result = jt.query(selectSQL, args, rowMapper); + for (E item : result) { + page.getPageItems().add(item); + } + return page; + } + + public Page fetchPageLimit(final JdbcTemplate jt, final String sqlCountRows, final String sqlFetchRows, + final Object args[], final int pageNo, final int pageSize, final RowMapper rowMapper) { + if (pageNo <= 0 || pageSize <= 0) { + throw new IllegalArgumentException("pageNo and pageSize must be greater than zero"); + } + // 查询当前记录总数 + Integer rowCountInt = jt.queryForObject(sqlCountRows, Integer.class); + if (rowCountInt == null) { + throw new IllegalArgumentException("fetchPageLimit error"); + } + final int rowCount = rowCountInt.intValue(); + + // 计算页数 + int pageCount = rowCount / pageSize; + if (rowCount > pageSize * pageCount) { + pageCount++; + } + + // 创建Page对象 + final Page page = new Page(); + page.setPageNumber(pageNo); + page.setPagesAvailable(pageCount); + page.setTotalCount(rowCount); + + if (pageNo > pageCount) { + return null; + } + + String selectSQL = sqlFetchRows; + if (PropertyUtil.isStandaloneMode()) { + selectSQL = selectSQL.replaceAll("(?i)LIMIT \\?,\\?", "OFFSET ? ROWS FETCH NEXT ? ROWS ONLY"); + } + + List result = jt.query(selectSQL, args, rowMapper); + for (E item : result) { + page.getPageItems().add(item); + } + return page; + } + + public Page fetchPageLimit(final JdbcTemplate jt, final String sqlCountRows, final Object args1[], final String sqlFetchRows, + final Object args2[], final int pageNo, final int pageSize, final RowMapper rowMapper) { + if (pageNo <= 0 || pageSize <= 0) { + throw new IllegalArgumentException("pageNo and pageSize must be greater than zero"); + } + // 查询当前记录总数 + Integer rowCountInt = jt.queryForObject(sqlCountRows, Integer.class, args1); + if (rowCountInt == null) { + throw new IllegalArgumentException("fetchPageLimit error"); + } + final int rowCount = rowCountInt.intValue(); + + // 计算页数 + int pageCount = rowCount / pageSize; + if (rowCount > pageSize * pageCount) { + pageCount++; + } + + // 创建Page对象 + final Page page = new Page(); + page.setPageNumber(pageNo); + page.setPagesAvailable(pageCount); + page.setTotalCount(rowCount); + + if (pageNo > pageCount) { + return null; + } + + String selectSQL = sqlFetchRows; + if (PropertyUtil.isStandaloneMode()) { + selectSQL = selectSQL.replaceAll("(?i)LIMIT \\?,\\?", "OFFSET ? ROWS FETCH NEXT ? ROWS ONLY"); + } + + List result = jt.query(selectSQL, args2, rowMapper); + for (E item : result) { + page.getPageItems().add(item); + } + return page; + } + + public Page fetchPageLimit(final JdbcTemplate jt, final String sqlFetchRows, + final Object args[], final int pageNo, final int pageSize, final RowMapper rowMapper) { + if (pageNo <= 0 || pageSize <= 0) { + throw new IllegalArgumentException("pageNo and pageSize must be greater than zero"); + } + // 创建Page对象 + final Page page = new Page(); + + String selectSQL = sqlFetchRows; + if (PropertyUtil.isStandaloneMode()) { + selectSQL = selectSQL.replaceAll("(?i)LIMIT \\?,\\?", "OFFSET ? ROWS FETCH NEXT ? ROWS ONLY"); + } + + List result = jt.query(selectSQL, args, rowMapper); + for (E item : result) { + page.getPageItems().add(item); + } + return page; + } + + public void updateLimit(final JdbcTemplate jt, final String sql, final Object args[]) { + String sqlUpdate = sql; + + if (PropertyUtil.isStandaloneMode()) { + sqlUpdate = sqlUpdate.replaceAll("limit \\?", "OFFSET 0 ROWS FETCH NEXT ? ROWS ONLY"); + } + + jt.update(sqlUpdate, args); + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/ParamUtils.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/ParamUtils.java new file mode 100644 index 00000000000..24b734938c3 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/ParamUtils.java @@ -0,0 +1,155 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +import java.util.Map; + +import org.apache.commons.lang.StringUtils; + +import com.alibaba.nacos.config.server.exception.NacosException; + +/** + * 参数合法性检查工具类 + * + * @author Nacos + * + */ +public class ParamUtils { + + private static char[] validChars = new char[] { '_', '-', '.', ':' }; + + private final static int TAG_MAX_LEN = 16; + + private final static int TANANT_MAX_LEN = 128; + + /** + * 白名单的方式检查, 合法的参数只能包含字母、数字、以及validChars中的字符, 并且不能为空 + * + * @param param + * @return + */ + public static boolean isValid(String param) { + if (param == null) { + return false; + } + int length = param.length(); + for (int i = 0; i < length; i++) { + char ch = param.charAt(i); + if (Character.isLetterOrDigit(ch)) { + continue; + } + else if (isValidChar(ch)) { + continue; + } + else { + return false; + } + } + return true; + } + + private static boolean isValidChar(char ch) { + for (char c : validChars) { + if (c == ch) { + return true; + } + } + return false; + } + + public static void checkParam(String dataId, String group, String datumId, String content) throws NacosException { + if (StringUtils.isBlank(dataId) || !ParamUtils.isValid(dataId.trim())) { + throw new NacosException(NacosException.INVALID_PARAM, "invalid dataId"); + } else if (StringUtils.isBlank(group) || !ParamUtils.isValid(group)) { + throw new NacosException(NacosException.INVALID_PARAM, "invalid group"); + } else if (StringUtils.isBlank(datumId) || !ParamUtils.isValid(datumId)) { + throw new NacosException(NacosException.INVALID_PARAM, "invalid datumId"); + } else if (StringUtils.isBlank(content)) { + throw new NacosException(NacosException.INVALID_PARAM, "content is blank"); + } else if (content.length() > PropertyUtil.getMaxContent()) { + throw new NacosException(NacosException.INVALID_PARAM, + "invalid content, over " + PropertyUtil.getMaxContent()); + } + } + + public static void checkParam(String tag) { + if (StringUtils.isNotBlank(tag)) { + if (!ParamUtils.isValid(tag.trim())) { + throw new IllegalArgumentException("invalid tag"); + } + if (tag.length() > TAG_MAX_LEN) { + throw new IllegalArgumentException("too long tag, over 16"); + } + } + } + + public static void checkTenant(String tenant) { + if (StringUtils.isNotBlank(tenant)) { + if (!ParamUtils.isValid(tenant.trim())) { + throw new IllegalArgumentException("invalid tenant"); + } + if (tenant.length() > TANANT_MAX_LEN) { + throw new IllegalArgumentException("too long tag, over 128"); + } + } + } + + public static void checkParam(Map configAdvanceInfo) throws NacosException { + for (Map.Entry configAdvanceInfoTmp : configAdvanceInfo.entrySet()) { + if ("config_tags".equals(configAdvanceInfoTmp.getKey())) { + if (configAdvanceInfoTmp.getValue() != null) { + String[] tagArr = ((String) configAdvanceInfoTmp.getValue()).split(","); + if (tagArr.length > 5) { + throw new NacosException(NacosException.INVALID_PARAM, "too much config_tags, over 5"); + } + for (String tag : tagArr) { + if (tag.length() > 64) { + throw new NacosException(NacosException.INVALID_PARAM, "too long tag, over 64"); + } + } + } + } else if ("desc".equals(configAdvanceInfoTmp.getKey())) { + if (configAdvanceInfoTmp.getValue() != null + && ((String) configAdvanceInfoTmp.getValue()).length() > 128) { + throw new NacosException(NacosException.INVALID_PARAM, "too long desc, over 128"); + } + } else if ("use".equals(configAdvanceInfoTmp.getKey())) { + if (configAdvanceInfoTmp.getValue() != null + && ((String) configAdvanceInfoTmp.getValue()).length() > 32) { + throw new NacosException(NacosException.INVALID_PARAM, "too long use, over 32"); + } + } else if ("effect".equals(configAdvanceInfoTmp.getKey())) { + if (configAdvanceInfoTmp.getValue() != null + && ((String) configAdvanceInfoTmp.getValue()).length() > 32) { + throw new NacosException(NacosException.INVALID_PARAM, "too long effect, over 32"); + } + } else if ("type".equals(configAdvanceInfoTmp.getKey())) { + if (configAdvanceInfoTmp.getValue() != null + && ((String) configAdvanceInfoTmp.getValue()).length() > 32) { + throw new NacosException(NacosException.INVALID_PARAM, "too long type, over 32"); + } + } else if ("schema".equals(configAdvanceInfoTmp.getKey())) { + if (configAdvanceInfoTmp.getValue() != null + && ((String) configAdvanceInfoTmp.getValue()).length() > 32768) { + throw new NacosException(NacosException.INVALID_PARAM, "too long schema, over 32768"); + } + } else { + throw new NacosException(NacosException.INVALID_PARAM, "invalid param"); + } + } + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/PropertyUtil.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/PropertyUtil.java new file mode 100644 index 00000000000..c33b837c082 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/PropertyUtil.java @@ -0,0 +1,276 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +import javax.annotation.PostConstruct; + +import org.slf4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +/** + * properties utils + * + * @author Nacos + * + */ +@Component +public class PropertyUtil { + + private final static Logger logger = LogUtil.defaultLog; + + private static int notifyConnectTimeout = 100; + private static int notifySocketTimeout = 200; + private static int maxHealthCheckFailCount = 12; + private static boolean isHealthCheck = true; + private static int maxContent = 10 * 1024 * 1024; + + /** + * 是否开启容量管理 + */ + private static boolean isManageCapacity = true; + /** + * 是否开启容量管理的限制检验功能,包括配置个数上限、配置内容大小限制等 + */ + private static boolean isCapacityLimitCheck = false; + /** + * 集群默认容量上限 + */ + private static int defaultClusterQuota = 100000; + /** + * 每个Group默认容量上限 + */ + private static int defaultGroupQuota = 200; + /** + * 每个Tenant默认容量上限 + */ + private static int defaultTenantQuota = 200; + /** + * 单个配置中content的最大大小,单位为字节 + */ + private static int defaultMaxSize = 100 * 1024; + /** + * 聚合数据子配置最大个数 + */ + private static int defaultMaxAggrCount = 10000; + /** + * 聚合数据单个子配置中content的最大大小,单位为字节 + */ + private static int defaultMaxAggrSize = 1024; + /** + * 初始化容量信息记录时,发现已经到达限额时的扩容百分比 + */ + private static int initialExpansionPercent = 100; + /** + * 修正容量信息表使用量(usage)的时间间隔,单位为秒 + */ + private static int correctUsageDelay = 10 * 60; + + private static boolean standaloneMode = false; + + @Autowired + private Environment env; + + static { + setStandaloneMode(Boolean.parseBoolean(System.getProperty("nacos.standalone", "false"))); + } + + @PostConstruct + public void init() { + try { + + setNotifyConnectTimeout(Integer.parseInt(env.getProperty("notifyConnectTimeout", "100"))); + logger.info("notifyConnectTimeout:{}", notifyConnectTimeout); + setNotifySocketTimeout(Integer.parseInt(env.getProperty("notifySocketTimeout", "200"))); + logger.info("notifySocketTimeout:{}", notifySocketTimeout); + setHealthCheck(Boolean.valueOf(env.getProperty("isHealthCheck", "true"))); + logger.info("isHealthCheck:{}", isHealthCheck); + setMaxHealthCheckFailCount(Integer.parseInt(env.getProperty("maxHealthCheckFailCount", "12"))); + logger.info("maxHealthCheckFailCount:{}", maxHealthCheckFailCount); + setMaxContent(Integer.parseInt(env.getProperty("maxContent", String.valueOf(maxContent)))); + logger.info("maxContent:{}", maxContent); + // 容量管理 + setManageCapacity(getBoolean("isManageCapacity", isManageCapacity)); + setCapacityLimitCheck(getBoolean("isCapacityLimitCheck", isCapacityLimitCheck)); + setDefaultClusterQuota(getInt("defaultClusterQuota", defaultClusterQuota)); + setDefaultGroupQuota(getInt("defaultGroupQuota", defaultGroupQuota)); + setDefaultTenantQuota(getInt("defaultTenantQuota", defaultTenantQuota)); + setDefaultMaxSize(getInt("defaultMaxSize", defaultMaxSize)); + setDefaultMaxAggrCount(getInt("defaultMaxAggrCount", defaultMaxAggrCount)); + setDefaultMaxAggrSize(getInt("defaultMaxAggrSize", defaultMaxAggrSize)); + setCorrectUsageDelay(getInt("correctUsageDelay", correctUsageDelay)); + setInitialExpansionPercent(getInt("initialExpansionPercent", initialExpansionPercent)); + + } catch (Exception e) { + logger.error("read application.properties failed", e); + } + } + + public static int getNotifyConnectTimeout() { + return notifyConnectTimeout; + } + + public static int getNotifySocketTimeout() { + return notifySocketTimeout; + } + + public static int getMaxHealthCheckFailCount() { + return maxHealthCheckFailCount; + } + + public static boolean isHealthCheck() { + return isHealthCheck; + } + + private boolean getBoolean(String key, boolean defaultValue) { + return Boolean.valueOf(getString(key, String.valueOf(defaultValue))); + } + + private int getInt(String key, int defaultValue) { + return Integer.parseInt(getString(key, String.valueOf(defaultValue))); + } + + private String getString(String key, String defaultValue) { + String value = env.getProperty(key); + if (value == null) { + return defaultValue; + } + logger.info("{}:{}", key, value); + return value; + } + + public String getProperty(String key) { + return env.getProperty(key); + } + + public String getProperty(String key, String defaultValue) { + return env.getProperty(key, defaultValue); + } + + public static int getMaxContent() { + return maxContent; + } + + public static boolean isManageCapacity() { + return isManageCapacity; + } + + public static int getDefaultClusterQuota() { + return defaultClusterQuota; + } + + public static boolean isCapacityLimitCheck() { + return isCapacityLimitCheck; + } + + public static int getDefaultGroupQuota() { + return defaultGroupQuota; + } + + public static int getDefaultTenantQuota() { + return defaultTenantQuota; + } + + public static int getInitialExpansionPercent() { + return initialExpansionPercent; + } + + public static int getDefaultMaxSize() { + return defaultMaxSize; + } + + public static int getDefaultMaxAggrCount() { + return defaultMaxAggrCount; + } + + public static int getDefaultMaxAggrSize() { + return defaultMaxAggrSize; + } + + public static int getCorrectUsageDelay() { + return correctUsageDelay; + } + + public static boolean isStandaloneMode() { + return standaloneMode; + } + + public static void setStandaloneMode(boolean standaloneMode) { + PropertyUtil.standaloneMode = standaloneMode; + } + + public static void setNotifyConnectTimeout(int notifyConnectTimeout) { + PropertyUtil.notifyConnectTimeout = notifyConnectTimeout; + } + + public static void setNotifySocketTimeout(int notifySocketTimeout) { + PropertyUtil.notifySocketTimeout = notifySocketTimeout; + } + + public static void setMaxHealthCheckFailCount(int maxHealthCheckFailCount) { + PropertyUtil.maxHealthCheckFailCount = maxHealthCheckFailCount; + } + + public static void setHealthCheck(boolean isHealthCheck) { + PropertyUtil.isHealthCheck = isHealthCheck; + } + + public static void setMaxContent(int maxContent) { + PropertyUtil.maxContent = maxContent; + } + + public static void setManageCapacity(boolean isManageCapacity) { + PropertyUtil.isManageCapacity = isManageCapacity; + } + + public static void setCapacityLimitCheck(boolean isCapacityLimitCheck) { + PropertyUtil.isCapacityLimitCheck = isCapacityLimitCheck; + } + + public static void setDefaultClusterQuota(int defaultClusterQuota) { + PropertyUtil.defaultClusterQuota = defaultClusterQuota; + } + + public static void setDefaultGroupQuota(int defaultGroupQuota) { + PropertyUtil.defaultGroupQuota = defaultGroupQuota; + } + + public static void setDefaultTenantQuota(int defaultTenantQuota) { + PropertyUtil.defaultTenantQuota = defaultTenantQuota; + } + + public static void setDefaultMaxSize(int defaultMaxSize) { + PropertyUtil.defaultMaxSize = defaultMaxSize; + } + + public static void setDefaultMaxAggrCount(int defaultMaxAggrCount) { + PropertyUtil.defaultMaxAggrCount = defaultMaxAggrCount; + } + + public static void setDefaultMaxAggrSize(int defaultMaxAggrSize) { + PropertyUtil.defaultMaxAggrSize = defaultMaxAggrSize; + } + + public static void setInitialExpansionPercent(int initialExpansionPercent) { + PropertyUtil.initialExpansionPercent = initialExpansionPercent; + } + + public static void setCorrectUsageDelay(int correctUsageDelay) { + PropertyUtil.correctUsageDelay = correctUsageDelay; + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/Protocol.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/Protocol.java new file mode 100644 index 00000000000..25c4cbffc99 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/Protocol.java @@ -0,0 +1,48 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +/** + * 用来处理协议相关的操作 + * + * @author zhidao + * @version 1.0 2011/05/03 + * + */ +public class Protocol { + /** + * 解析类于2.0.4(major.minor.bug-fix这样的版本为数字) + * + * @param version + * @return + */ + public static int getVersionNumber(String version) { + if (version == null) { + return -1; + } + String[] vs = version.split("\\."); + int sum = 0; + for (int i = 0; i < vs.length; i++) { + try { + sum = sum * 10 + Integer.parseInt(vs[i]); + } + catch (Exception e) { + // ignore + } + } + return sum; + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/RegexParser.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/RegexParser.java new file mode 100644 index 00000000000..a8c321b009d --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/RegexParser.java @@ -0,0 +1,82 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +import org.apache.commons.lang.CharUtils; +import org.apache.commons.lang.NullArgumentException; + + + +/** + * 用于ConfigCenter可支持的通配字符通配判定以及标准正则转换的通用类 + * + * @author tianhu E-mail: + * @version 创建时间:2008-12-30 下午07:09:52 类说明 + */ +public class RegexParser { + + private final static char QUESTION_MARK = '?'; + + /** + * 替换输入字符串中非正则特殊字符为标准正则表达式字符串;
+ * '*'替换为 ‘.*’ '?'替换为'{n}',n为连续?的个数;
+ * 其他非字母或数字的特殊字符前均添加'\'. + * + * @param regex + * @return + */ + static public String regexFormat(String regex) { + if (regex == null) { + throw new NullArgumentException("regex string can't be null"); + } + StringBuffer result = new StringBuffer(); + result.append("^"); + for (int i = 0; i < regex.length(); i++) { + char ch = regex.charAt(i); + if (CharUtils.isAsciiAlphanumeric(ch) || CharUtils.isAsciiNumeric(ch)) { + result.append(ch); + } else if (ch == '*') { + result.append(".*"); + } else if (ch == QUESTION_MARK) { + int j = 0; + for (; j < regex.length() - i && ch == QUESTION_MARK; j++) { + ch = regex.charAt(i + j); + } + if (j == regex.length() - i) { + result.append(".{" + j + "}"); + break; + } else { + j -= 1; + result.append(".{" + (j) + "}"); + i += j - 1; + } + } else { + result.append("\\" + ch); + } + } + result.append("$"); + return result.toString(); + } + + static public boolean containsWildcard(String regex) { + return (regex.contains("?") || regex.contains("*")); + } + + public static void main(String[] args) { + String str = "com.taobao.uic.*"; + System.out.println(str + " -> " + regexFormat(str)); + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/RequestUtil.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/RequestUtil.java new file mode 100644 index 00000000000..c3248f84ae0 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/RequestUtil.java @@ -0,0 +1,38 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +import javax.servlet.http.HttpServletRequest; + +/** + * Request util + * @author Nacos + * + */ +public class RequestUtil { + + public static final String CLIENT_APPNAME_HEADER = "Client-AppName"; + + public static String getRemoteIp(HttpServletRequest request) { + String nginxHeader = request.getHeader("X-Real-IP"); + return (nginxHeader == null) ? request.getRemoteAddr() : nginxHeader; + } + + public static String getAppName(HttpServletRequest request) { + return request.getHeader("Client-AppName"); + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/ResourceUtils.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/ResourceUtils.java new file mode 100644 index 00000000000..f00f95ba26a --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/ResourceUtils.java @@ -0,0 +1,214 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.URL; +import java.util.Properties; + +import com.alibaba.nacos.config.server.constant.Constants; + + +/** + * resource util + * @author boyan + * @date 2010-5-4 + */ +public class ResourceUtils { + + /** + * Returns the URL of the resource on the classpath + * + * @param resource + * The resource to find + * @throws IOException + * If the resource cannot be found or read + * @return The resource + */ + public static URL getResourceURL(String resource) throws IOException { + ClassLoader loader = ResourceUtils.class.getClassLoader(); + return getResourceURL(loader, resource); + } + + + /** + * Returns the URL of the resource on the classpath + * + * @param loader + * The classloader used to load the resource + * @param resource + * The resource to find + * @throws IOException + * If the resource cannot be found or read + * @return The resource + */ + public static URL getResourceURL(ClassLoader loader, String resource) throws IOException { + URL url = null; + if (loader != null) { + url = loader.getResource(resource); + } + if (url == null) { + url = ClassLoader.getSystemResource(resource); + } + if (url == null) { + throw new IOException("Could not find resource " + resource); + } + return url; + } + + + /** + * Returns a resource on the classpath as a Stream object + * + * @param resource + * The resource to find + * @throws IOException + * If the resource cannot be found or read + * @return The resource + */ + public static InputStream getResourceAsStream(String resource) throws IOException { + ClassLoader loader = ResourceUtils.class.getClassLoader(); + return getResourceAsStream(loader, resource); + } + + + /** + * Returns a resource on the classpath as a Stream object + * + * @param loader + * The classloader used to load the resource + * @param resource + * The resource to find + * @throws IOException + * If the resource cannot be found or read + * @return The resource + */ + public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException { + InputStream in = null; + if (loader != null) { + in = loader.getResourceAsStream(resource); + } + if (in == null) { + in = ClassLoader.getSystemResourceAsStream(resource); + } + if (in == null) { + throw new IOException("Could not find resource " + resource); + } + return in; + } + + + /** + * Returns a resource on the classpath as a Properties object + * + * @param resource + * The resource to find + * @throws IOException + * If the resource cannot be found or read + * @return The resource + */ + public static Properties getResourceAsProperties(String resource) throws IOException { + ClassLoader loader = ResourceUtils.class.getClassLoader(); + return getResourceAsProperties(loader, resource); + } + + + /** + * Returns a resource on the classpath as a Properties object + * + * @param loader + * The classloader used to load the resource + * @param resource + * The resource to find + * @throws IOException + * If the resource cannot be found or read + * @return The resource + */ + public static Properties getResourceAsProperties(ClassLoader loader, String resource) throws IOException { + Properties props = new Properties(); + InputStream in = null; + String propfile = resource; + in = getResourceAsStream(loader, propfile); + props.load(in); + in.close(); + return props; + } + + + /** + * Returns a resource on the classpath as a Reader object + * + * @param resource + * The resource to find + * @throws IOException + * If the resource cannot be found or read + * @return The resource + */ + public static InputStreamReader getResourceAsReader(String resource) throws IOException { + return new InputStreamReader(getResourceAsStream(resource), Constants.ENCODE); + } + + + /** + * Returns a resource on the classpath as a Reader object + * + * @param loader + * The classloader used to load the resource + * @param resource + * The resource to find + * @throws IOException + * If the resource cannot be found or read + * @return The resource + */ + public static Reader getResourceAsReader(ClassLoader loader, String resource) throws IOException { + return new InputStreamReader(getResourceAsStream(loader, resource), Constants.ENCODE); + } + + + /** + * Returns a resource on the classpath as a File object + * + * @param resource + * The resource to find + * @throws IOException + * If the resource cannot be found or read + * @return The resource + */ + public static File getResourceAsFile(String resource) throws IOException { + return new File(getResourceURL(resource).getFile()); + } + + + /** + * Returns a resource on the classpath as a File object + * + * @param loader + * The classloader used to load the resource + * @param resource + * The resource to find + * @throws IOException + * If the resource cannot be found or read + * @return The resource + */ + public static File getResourceAsFile(ClassLoader loader, String resource) throws IOException { + return new File(getResourceURL(loader, resource).getFile()); + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/ResponseUtil.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/ResponseUtil.java new file mode 100644 index 00000000000..2437ddcbbf7 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/ResponseUtil.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +import static com.alibaba.nacos.config.server.utils.LogUtil.defaultLog; + +import java.io.IOException; +import javax.servlet.http.HttpServletResponse; + +/** + * write response + * + * @author Nacos + * + */ +public class ResponseUtil { + + public static void writeErrMsg(HttpServletResponse response, int httpCode, + String msg) { + response.setStatus(httpCode); + try { + response.getWriter().println(msg); + } catch (IOException e) { + defaultLog.error("ResponseUtil:writeErrMsg wrong", e); + } + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/RunningConfigUtils.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/RunningConfigUtils.java new file mode 100644 index 00000000000..a1dccf2b8f4 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/RunningConfigUtils.java @@ -0,0 +1,68 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +import javax.servlet.ServletContext; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.web.context.WebServerInitializedEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +/** + * Running config + * @author dungu.zpf + */ +@Component +public class RunningConfigUtils implements ApplicationListener { + + private static int serverPort; + + private static String contextPath; + + private static String clusterName = "serverlist"; + + @Autowired + private ServletContext servletContext; + + @Override + public void onApplicationEvent(WebServerInitializedEvent event) { + + setServerPort(event.getWebServer().getPort()); + setContextPath(servletContext.getContextPath()); + } + + public static int getServerPort() { + return serverPort; + } + + public static String getContextPath() { + return contextPath; + } + + public static String getClusterName() { + return clusterName; + } + + public static void setServerPort(int serverPort) { + RunningConfigUtils.serverPort = serverPort; + } + + public static void setContextPath(String contextPath) { + RunningConfigUtils.contextPath = contextPath; + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/SimpleCache.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/SimpleCache.java new file mode 100644 index 00000000000..7f2182ecafd --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/SimpleCache.java @@ -0,0 +1,58 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + + +/** + * 一个带TTL的简单Cache,对于过期的entry没有清理 + * + * @author fengHan, jiuRen + * + * @param + */ +public class SimpleCache { + + final ConcurrentMap> cache = new ConcurrentHashMap>(); + + private static class CacheEntry { + final long expireTime; + final E value; + + public CacheEntry(E value, long expire) { + this.expireTime = expire; + this.value = value; + } + } + + public void put(String key, E e, long ttlMs) { + if (key == null || e == null) { + return; + } + CacheEntry entry = new CacheEntry(e, System.currentTimeMillis() + ttlMs); + cache.put(key, entry); + } + + public E get(String key) { + CacheEntry entry = cache.get(key); + if (entry != null && entry.expireTime > System.currentTimeMillis()) { + return entry.value; + } + return null; + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/SimpleFlowData.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/SimpleFlowData.java new file mode 100644 index 00000000000..e7dbc176802 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/SimpleFlowData.java @@ -0,0 +1,124 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Simple Flow data + * @author Nacos + * + */ +public class SimpleFlowData { + private int index = 0; + private AtomicInteger[] data; + private int average; + private int slotCount; + + @SuppressWarnings("PMD.ThreadPoolCreationRule") + private ScheduledExecutorService timer = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { + + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setName("nacos flow control thread"); + t.setDaemon(true); + return t; + } + + }); + + + public SimpleFlowData(int slotCount, int interval) { + this.slotCount = slotCount; + data = new AtomicInteger[slotCount]; + for (int i = 0; i < data.length; i++) { + data[i] = new AtomicInteger(0); + } + timer.scheduleAtFixedRate(new Runnable() { + + public void run() { + rotateSlot(); + } + + }, interval, interval, TimeUnit.MILLISECONDS); + } + + + public int addAndGet(int count) { + return data[index].addAndGet(count); + } + + + public int incrementAndGet() { + return data[index].incrementAndGet(); + } + + + public void rotateSlot() { + int total = 0; + + for (int i = 0; i < slotCount; i++) { + total += data[i].get(); + } + + average = total / slotCount; + + index = (index + 1) % slotCount; + data[index].set(0); + } + + + public int getCurrentCount() { + return data[index].get(); + } + + + public int getAverageCount() { + return this.average; + } + + + public int getSlotCount() { + return this.slotCount; + } + + + public String getSlotInfo() { + StringBuilder sb = new StringBuilder(); + + int index = this.index + 1; + + for (int i = 0; i < slotCount; i++) { + if (i > 0) { + sb.append(" "); + } + sb.append(this.data[(i + index) % slotCount].get()); + } + return sb.toString(); + } + + + public int getCount(int prevStep) { + prevStep = prevStep % this.slotCount; + int index = (this.index + this.slotCount - prevStep) % this.slotCount; + return this.data[index].intValue(); + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/SimpleIPFlowData.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/SimpleIPFlowData.java new file mode 100644 index 00000000000..7647d42aff6 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/SimpleIPFlowData.java @@ -0,0 +1,113 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + + +/** + * 根据IP进行流控, 控制单个IP的数量以及IP总量 + * + * @author leiwen.zh + * + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public class SimpleIPFlowData { + + private AtomicInteger[] data; + + private int slotCount; + + private int averageCount; + + @SuppressWarnings("PMD.ThreadPoolCreationRule") + private ScheduledExecutorService timer = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { + + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setName("nacos ip flow control thread"); + t.setDaemon(true); + return t; + } + + }); + + class DefaultIPFlowDataManagerTask implements Runnable { + + public void run() { + rotateSlot(); + } + + } + + + public SimpleIPFlowData(int slotCount, int interval) { + if (slotCount <= 0) { + this.slotCount = 1; + } + else { + this.slotCount = slotCount; + } + data = new AtomicInteger[slotCount]; + for (int i = 0; i < data.length; i++) { + data[i] = new AtomicInteger(0); + } + timer.scheduleAtFixedRate(new DefaultIPFlowDataManagerTask(), interval, interval, TimeUnit.MILLISECONDS); + } + + + public int incrementAndGet(String ip) { + int index = 0; + if (ip != null) { + index = ip.hashCode() % slotCount; + } + if (index < 0) { + index = -index; + } + return data[index].incrementAndGet(); + } + + + public void rotateSlot() { + int totalCount = 0; + for (int i = 0; i < slotCount; i++) { + totalCount += data[i].get(); + data[i].set(0); + } + this.averageCount = totalCount / this.slotCount; + } + + + public int getCurrentCount(String ip) { + int index = 0; + if (ip != null) { + index = ip.hashCode() % slotCount; + } + if (index < 0) { + index = -index; + } + return data[index].get(); + } + + + public int getAverageCount() { + return this.averageCount; + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/SimpleReadWriteLock.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/SimpleReadWriteLock.java new file mode 100644 index 00000000000..255152ba8d4 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/SimpleReadWriteLock.java @@ -0,0 +1,63 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + + +/** + * 最简单的读写锁实现。要求加锁和解锁必须成对调用。 + * @author Nacos + * + */ +public class SimpleReadWriteLock { + + public synchronized boolean tryReadLock() { + if (isWriteLocked()) { + return false; + } else { + status++; + return true; + } + } + + public synchronized void releaseReadLock() { + status--; + } + + public synchronized boolean tryWriteLock() { + if (!isFree()) { + return false; + } else { + status = -1; + return true; + } + } + + public synchronized void releaseWriteLock() { + status = 0; + } + + private boolean isWriteLocked() { + return status < 0; + } + private boolean isFree() { + return status == 0; + } + + /** + * 零表示没有锁;负数表示加写锁;正数表示加读锁,数值表示读锁的个数。 + */ + private int status = 0; +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/SingletonRepository.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/SingletonRepository.java new file mode 100644 index 00000000000..e09fb810365 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/SingletonRepository.java @@ -0,0 +1,61 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +import java.util.concurrent.ConcurrentHashMap; + + +/** + * 避免多个相同内容的实例的工具类。比如,可以用来缓存客户端IP。 + * @author Nacos + */ +public class SingletonRepository { + + public SingletonRepository() { + // 初始化大小2^16, 这个容器本身大概占用50k的内存,避免不停扩容 + shared = new ConcurrentHashMap(1 << 16); + } + + public T getSingleton(T obj) { + T previous = shared.putIfAbsent(obj, obj); + return (null == previous) ? obj : previous; + } + + public int size() { + return shared.size(); + } + + /** + * 必须小心使用。 + * @param obj obj + */ + public void remove(Object obj) { + shared.remove(obj); + } + + private final ConcurrentHashMap shared; + + /** + * DataId和Group的缓存。 + */ + static public class DataIdGroupIdCache { + static public String getSingleton(String str) { + return cache.getSingleton(str); + } + + static SingletonRepository cache = new SingletonRepository(); + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/StatConstants.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/StatConstants.java new file mode 100644 index 00000000000..8bf22a3a1d9 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/StatConstants.java @@ -0,0 +1,37 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +/** + * Stat constant + * @author Nacos + * + */ +public class StatConstants { + private StatConstants() { + } + + public static final String APP_NAME = "nacos"; + + public static final String STAT_AVERAGE_HTTP_GET_OK = "AverageHttpGet_OK"; + + public static final String STAT_AVERAGE_HTTP_GET_NOT_MODIFIED = "AverageHttpGet_Not_Modified"; + + public static final String STAT_AVERAGE_HTTP_GET_OTHER = "AverageHttpGet_Other_Status"; + + public static final String STAT_AVERAGE_HTTP_POST_CHECK = "AverageHttpPost_Check"; + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/StringUtils.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/StringUtils.java new file mode 100644 index 00000000000..8b0f63336e2 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/StringUtils.java @@ -0,0 +1,69 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +/** + * 替代common-lang中类,减少依赖 + * @author Nacos + * + */ +public class StringUtils { + + public static final int INDEX_NOT_FOUND = -1; + + public static boolean isBlank(String str) { + int strLen; + if (str == null || (strLen = str.length()) == 0) { + return true; + } + for (int i = 0; i < strLen; i++) { + if ((Character.isWhitespace(str.charAt(i)) == false)) { + return false; + } + } + return true; + } + + public static boolean isNotEmpty(String str) { + return !StringUtils.isEmpty(str); + } + + public static boolean isEmpty(String str) { + return str == null || str.length() == 0; + } + + public static String defaultIfEmpty(String str, String defaultStr) { + return StringUtils.isEmpty(str) ? defaultStr : str; + } + + public static boolean equals(String str1, String str2) { + return str1 == null ? str2 == null : str1.equals(str2); + } + + public static String substringBetween(String str, String open, String close) { + if (str == null || open == null || close == null) { + return null; + } + int start = str.indexOf(open); + if (start != INDEX_NOT_FOUND) { + int end = str.indexOf(close, start + open.length()); + if (end != INDEX_NOT_FOUND) { + return str.substring(start + open.length(), end); + } + } + return null; + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/SystemConfig.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/SystemConfig.java new file mode 100644 index 00000000000..cc70258d039 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/SystemConfig.java @@ -0,0 +1,65 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.Enumeration; + +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * System config + * @author Nacos + * + */ +public class SystemConfig { + + public static final String LOCAL_IP = getHostAddress(); + + private static final Logger log = LoggerFactory.getLogger(SystemConfig.class); + + private static String getHostAddress() { + String address = System.getProperty("nacos.server.ip"); + if (StringUtils.isNotEmpty(address)) { + return address; + } else { + address = "127.0.0.1"; + } + try { + Enumeration en = NetworkInterface.getNetworkInterfaces(); + while (en.hasMoreElements()) { + NetworkInterface ni = en.nextElement(); + Enumeration ads = ni.getInetAddresses(); + while (ads.hasMoreElements()) { + InetAddress ip = ads.nextElement(); + // 兼容集团不规范11网段 + if (!ip.isLoopbackAddress() + && ip.getHostAddress().indexOf(":") == -1 + /* && ip.isSiteLocalAddress() */) { + return ip.getHostAddress(); + } + } + } + } catch (Exception e) { + log.error("get local host address error", e); + } + return address; + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/ThreadUtil.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/ThreadUtil.java new file mode 100644 index 00000000000..0b7780681c6 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/ThreadUtil.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +/** + * Thread util + * @author Nacos + * + */ +public class ThreadUtil { + + /** + * 通过内核数,算出合适的线程数;1.5-2倍cpu内核数 + * @return thread count + */ + public static int getSuitableThreadCount() { + final int coreCount = Runtime.getRuntime().availableProcessors(); + int workerCount = 1; + while (workerCount < coreCount * THREAD_MULTIPLER) { + workerCount <<= 1; + } + return workerCount; + } + + private final static int THREAD_MULTIPLER = 2; +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/TimeUtils.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/TimeUtils.java new file mode 100644 index 00000000000..644af4811d6 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/TimeUtils.java @@ -0,0 +1,45 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +import java.sql.Timestamp; +import java.util.Calendar; +import java.util.Date; + +import org.apache.commons.lang.time.FastDateFormat; +/** + * Time util + * @author Nacos + * + */ +public class TimeUtils { + + public static Timestamp getCurrentTime() { + Date date = new Date(); + return new Timestamp(date.getTime()); + } + public static void main(String[] args) { + System.out.println(getCurrentTime().toString()); + } + + static public String getCurrentTimeStr() { + Calendar c = Calendar.getInstance(); + c.setTime(new Date()); + c.get(Calendar.HOUR); + FastDateFormat format = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss"); + return format.format(c.getTime()); + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/TimeoutUtils.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/TimeoutUtils.java new file mode 100644 index 00000000000..d3a41b47a67 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/TimeoutUtils.java @@ -0,0 +1,104 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +import java.util.concurrent.atomic.AtomicLong; + + +/** + * 处理超时的工具类, 用于客户端获取数据的总体超时。 每次从网络获取完数据后, 累计totalTime, 每次从网络获取数据前, + * 检查totalTime是否大于totalTimeout, 是则说明总体超时, totalTime有失效时间, 每次从网络获取数据前, 检查是否失效, + * 失效则重置totalTime, 重新开始累计 + * + * @author leiwen.zh + * + */ +public class TimeoutUtils { + + /** + * 累计的获取数据消耗的时间, 单位ms + */ + private final AtomicLong totalTime = new AtomicLong(0L); + + private volatile long lastResetTime; + + private volatile boolean initialized = false; + + /** + * 获取数据的总体超时, 单位ms + */ + private long totalTimeout; + /** + * 累计的获取数据消耗的时间的过期时间, 单位ms + */ + private long invalidThreshold; + + + public TimeoutUtils(long totalTimeout, long invalidThreshold) { + this.totalTimeout = totalTimeout; + this.invalidThreshold = invalidThreshold; + } + + + public synchronized void initLastResetTime() { + if (initialized) { + return; + } + lastResetTime = System.currentTimeMillis(); + initialized = true; + } + + + /** + * 累计总的时间 + * + * @param timeout + */ + public void addTotalTime(long time) { + totalTime.addAndGet(time); + } + + + /** + * 判断是否超时 + * + * @return + */ + public boolean isTimeout() { + return totalTime.get() > this.totalTimeout; + } + + + /** + * 总的时间清零 + */ + public void resetTotalTime() { + if (isTotalTimeExpired()) { + totalTime.set(0L); + lastResetTime = System.currentTimeMillis(); + } + } + + + public AtomicLong getTotalTime() { + return totalTime; + } + + + private boolean isTotalTimeExpired() { + return System.currentTimeMillis() - lastResetTime > this.invalidThreshold; + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/TraceLogUtil.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/TraceLogUtil.java new file mode 100644 index 00000000000..c44af306af1 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/TraceLogUtil.java @@ -0,0 +1,38 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Trace Util + * @author Nacos + * + */ +public class TraceLogUtil { + /** + * 记录server各个接口的请求记录 + */ + public static Logger requestLog = LoggerFactory.getLogger("com.alibaba.nacos.config.request"); + + /** + * 记录各个client的轮询请求记录 + */ + public static Logger pollingLog = LoggerFactory.getLogger("com.alibaba.nacos.config.polling"); + + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/UrlAnalysisUtils.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/UrlAnalysisUtils.java new file mode 100644 index 00000000000..261b3250fa6 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/UrlAnalysisUtils.java @@ -0,0 +1,75 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.alibaba.nacos.config.server.constant.Constants; + + +/** + * 分析url的工具类 + * + * @author leiwen.zh + * + */ +public class UrlAnalysisUtils { + + private static Pattern urlPattern = Pattern.compile("^(\\w+://)?([\\w\\.]+:)(\\d*)?(\\??.*)"); + + + public static String getContentIdentity(String content) { + + if (!verifyIncrementPubContent(content)) { + return null; + } + + Matcher matcher = urlPattern.matcher(content); + StringBuilder buf = new StringBuilder(); + if (matcher.find()) { + String scheme = matcher.group(1); + String address = matcher.group(2); + String port = matcher.group(3); + if (scheme != null) { + buf.append(scheme); + } + buf.append(address); + if (port != null) { + buf.append(port); + } + } + return buf.toString(); + } + + + private static boolean verifyIncrementPubContent(String content) { + + if (content == null || content.length() == 0) { + return false; + } + for (int i = 0; i < content.length(); i++) { + char c = content.charAt(i); + if (c == '\r' || c == '\n') { + return false; + } + if (c == Constants.WORD_SEPARATOR.charAt(0)) { + return false; + } + } + return true; + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/event/EventDispatcher.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/event/EventDispatcher.java new file mode 100644 index 00000000000..a6c8dd19d7c --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/event/EventDispatcher.java @@ -0,0 +1,148 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils.event; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Event dispatcher + * + * @author Nacos + * + */ +public class EventDispatcher { + + /** + * add event listener + */ + static public void addEventListener(AbstractEventListener listener) { + for (Class type : listener.interest()) { + getEntry(type).listeners.addIfAbsent(listener); + } + } + + /** + * fire event, notify listeners. + */ + static public void fireEvent(Event event) { + if (null == event) { + throw new IllegalArgumentException(); + } + + for (AbstractEventListener listener : getEntry(event.getClass()).listeners) { + try { + listener.onEvent(event); + } catch (Exception e) { + log.error(e.toString(), e); + } + } + } + + /** + * For only test purpose + */ + static public void clear() { + LISTENER_HUB.clear(); + } + + /** + * get event listener for eventType. Add Entry if not exist. + */ + static Entry getEntry(Class eventType) { + for (;;) { + for (Entry entry : LISTENER_HUB) { + if (entry.eventType == eventType) { + return entry; + } + } + + Entry tmp = new Entry(eventType); + /** + * false means already exists + */ + if (LISTENER_HUB.addIfAbsent(tmp)) { + return tmp; + } + } + } + + static private class Entry { + final Class eventType; + final CopyOnWriteArrayList listeners; + + Entry(Class type) { + eventType = type; + listeners = new CopyOnWriteArrayList(); + } + + @Override + public boolean equals(Object obj) { + if (null == obj || obj.getClass() != getClass()) { + return false; + } + if (this == obj) { + return true; + } + return eventType == ((Entry) obj).eventType; + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + } + + + static private final Logger log = LoggerFactory.getLogger(EventDispatcher.class); + + static final CopyOnWriteArrayList LISTENER_HUB = new CopyOnWriteArrayList(); + + + static public interface Event { + } + + static public abstract class AbstractEventListener { + + public AbstractEventListener() { + /** + * automatic register + */ + EventDispatcher.addEventListener(this); + } + + /** + * 感兴趣的事件列表 + * + * @return event list + */ + abstract public List> interest(); + + /** + * 处理事件 + * + * @param event + * event + */ + abstract public void onEvent(Event event); + } + +} diff --git a/config/src/main/resources/application.properties b/config/src/main/resources/application.properties new file mode 100644 index 00000000000..baadf01b7bb --- /dev/null +++ b/config/src/main/resources/application.properties @@ -0,0 +1,17 @@ +# spring +management.security.enabled=false +server.servlet.context-path=/nacos +server.port=8080 + +db.num=2 +db.url.0=url1 +db.url.1=url2 +db.user=user +db.password=pwd + +spring.http.encoding.force=true +spring.http.encoding.charset=UTF-8 +spring.http.encoding.enabled=true +server.tomcat.uri-encoding=UTF-8 +spring.messages.encoding=UTF-8 +security.headers.content-type=application/json;charset=UTF-8 \ No newline at end of file diff --git a/config/src/main/resources/banner.txt b/config/src/main/resources/banner.txt new file mode 100644 index 00000000000..4ebabe6fa06 --- /dev/null +++ b/config/src/main/resources/banner.txt @@ -0,0 +1,15 @@ + + ,--. + ,--.'| + ,--,: : | +,`--.'`| ' : ,---. +| : : | | ' ,'\ .--.--. +: | \ | : ,--.--. ,---. / / | / / ' +| : ' '; | / \ / \. ; ,. :| : /`./ +' ' ;. ;.--. .-. | / / '' | |: :| : ;_ +| | | \ | \__\/: . .. ' / ' | .; : \ \ `. +' : | ; .' ," .--.; |' ; :__| : | `----. \ +| | '`--' / / ,. |' | '.'|\ \ / / /`--' / +' : | ; : .' \ : : `----' '--'. / +; |.' | , .-./\ \ / `--'---' +'---' `--`---' `----' diff --git a/config/src/main/resources/diamond-app-collector.sql b/config/src/main/resources/diamond-app-collector.sql new file mode 100644 index 00000000000..39128570cb2 --- /dev/null +++ b/config/src/main/resources/diamond-app-collector.sql @@ -0,0 +1,31 @@ +CREATE TABLE `app_list` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', + `app_name` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'app_name', + `is_dynamic_collect_disabled` BIT(1) DEFAULT 0, + `last_sub_info_collected_time` datetime DEFAULT '1970-01-01 08:00:00.0', + `sub_info_lock_owner` varchar(128) COLLATE utf8_bin COMMENT 'lock owner', + `sub_info_lock_time` datetime DEFAULT '1970-01-01 08:00:00.0', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_appname` (`app_name`) +) ENGINE=InnoDB AUTO_INCREMENT=65535 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='application list'; + +CREATE TABLE `app_configdata_relation_subs` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', + `app_name` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'app_name', + `data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id', + `group_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'group_id', + `gmt_modified` datetime DEFAULT '2010-05-05 00:00:00' COMMENT '޸ʱ', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_app_sub_config_datagroup` (`app_name`,`data_id`,`group_id`) +) ENGINE=InnoDB AUTO_INCREMENT=565666 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='app_configdata_relation_subs'; + + +CREATE TABLE `app_configdata_relation_pubs` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', + `app_name` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'app_name', + `data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id', + `group_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'group_id', + `gmt_modified` datetime DEFAULT '2010-05-05 00:00:00' COMMENT '޸ʱ', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_app_pub_config_datagroup` (`app_name`,`data_id`,`group_id`) +) ENGINE=InnoDB AUTO_INCREMENT=565666 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='app_configdata_relation_pubs'; \ No newline at end of file diff --git a/config/src/main/resources/diamond-db.sql b/config/src/main/resources/diamond-db.sql new file mode 100644 index 00000000000..86d2a03a044 --- /dev/null +++ b/config/src/main/resources/diamond-db.sql @@ -0,0 +1,133 @@ +CREATE TABLE `config_info` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `data_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'data_id', + `group_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'group_id', + `content` longtext NOT NULL COMMENT 'content', + `md5` varchar(32) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT 'md5', + `src_user` text COMMENT 'src_user', + `src_ip` varchar(20) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT 'src_ip', + `gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT 'create', + `gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT 'modified', + `app_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT 'app_name', + `tenant_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT '' COMMENT '⻧ֶ', + `c_desc` varchar(256) DEFAULT NULL COMMENT 'c_desc', + `c_use` varchar(64) DEFAULT NULL COMMENT 'c_use', + `effect` varchar(64) DEFAULT NULL COMMENT 'effect', + `type` varchar(64) DEFAULT NULL COMMENT 'type', + `c_schema` text COMMENT 'c_schema', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`), + KEY `idx_dataid_gmt_modified` (`data_id`,`gmt_modified`), + KEY `idx_gmt_modified` (`gmt_modified`), + KEY `idx_appname` (`app_name`), + KEY `idx_groupid` (`group_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='config_info'; + +CREATE TABLE `config_info_beta` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', + `data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id', + `group_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'group_id', + `tenant_id` varchar(128) COLLATE utf8_bin default '' COMMENT 'tenant_id', + `app_name` varchar(128) COLLATE utf8_bin COMMENT 'app_name', + `content` longtext CHARACTER SET utf8 NOT NULL COMMENT 'content', + `beta_ips` varchar(1024) COLLATE utf8_bin COMMENT 'betaIps', + `md5` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'md5', + `gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT 'ʱ', + `gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '޸ʱ', + `src_user` text CHARACTER SET utf8 COMMENT '', + `src_ip` varchar(20) COLLATE utf8_bin DEFAULT NULL COMMENT '', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`) +) ENGINE=InnoDB AUTO_INCREMENT=565666 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta'; + +CREATE TABLE `config_info_tag` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', + `data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id', + `group_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'group_id', + `tenant_id` varchar(128) COLLATE utf8_bin default '' COMMENT 'tenant_id', + `tag_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'tag_id', + `app_name` varchar(128) COLLATE utf8_bin COMMENT 'app_name', + `content` longtext CHARACTER SET utf8 NOT NULL COMMENT 'content', + `md5` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'md5', + `gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT 'ʱ', + `gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '޸ʱ', + `src_user` text CHARACTER SET utf8 COMMENT '', + `src_ip` varchar(20) COLLATE utf8_bin DEFAULT NULL COMMENT '', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`) +) ENGINE=InnoDB AUTO_INCREMENT=565666 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag'; + +CREATE TABLE `config_info_aggr` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', + `data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id', + `group_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'group_id', + `tenant_id` varchar(128) COLLATE utf8_bin default '' COMMENT 'tenant_id', + `datum_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'datum_id', + `app_name` varchar(128) COLLATE utf8_bin COMMENT 'app_name', + `content` longtext COLLATE utf8_bin NOT NULL COMMENT '', + `gmt_modified` datetime NOT NULL COMMENT '޸ʱ', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='ۺݱ'; + +CREATE TABLE `his_config_info` ( + `id` bigint(64) unsigned NOT NULL, + `nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `data_id` varchar(255) COLLATE utf8_bin NOT NULL, + `group_id` varchar(128) COLLATE utf8_bin NOT NULL, + `tenant_id` varchar(128) COLLATE utf8_bin default '' COMMENT 'tenant_id', + `app_name` varchar(128) COLLATE utf8_bin COMMENT 'app_name', + `content` longtext CHARACTER SET utf8 NOT NULL, + `md5` varchar(32) CHARACTER SET utf8 DEFAULT NULL, + `gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00', + `gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00', + `src_user` text CHARACTER SET utf8, + `src_ip` varchar(20) COLLATE utf8_bin DEFAULT NULL, + `op_type` char(10) COLLATE utf8_bin DEFAULT NULL, + PRIMARY KEY (`nid`), + KEY `idx_gmt_create` (`gmt_create`), + KEY `idx_gmt_modified` (`gmt_modified`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +CREATE TABLE `config_tags_relation` ( + `id` bigint(20) NOT NULL COMMENT 'id', + `tag_name` varchar(128) NOT NULL COMMENT 'tag_name', + `tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type', + `data_id` varchar(255) NOT NULL COMMENT 'data_id', + `group_id` varchar(128) NOT NULL COMMENT 'group_id', + `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id', + `nid` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'nid', + PRIMARY KEY (`nid`), + UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`), + KEY `idx_tenant_id` (`tenant_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='config_tag_relation'; + +CREATE TABLE `group_capacity` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID', + `group_id` varchar(128) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT 'Group IDַʾȺ', + `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '0ʾʹĬֵ', + `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'ʹ', + `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'ôСޣλΪֽڣ0ʾʹĬֵ', + `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'ۺ0ʾʹĬֵ', + `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'ۺݵôСޣλΪֽڣ0ʾʹĬֵ', + `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'ʷ', + `gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT 'ʱ', + `gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '޸ʱ', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_group_id` (`group_id`) +) ENGINE=InnoDB AUTO_INCREMENT=1362 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='ȺGroupϢ'; + +CREATE TABLE `tenant_capacity` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID', + `tenant_id` varchar(128) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT 'Tenant ID', + `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '0ʾʹĬֵ', + `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'ʹ', + `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'ôСޣλΪֽڣ0ʾʹĬֵ', + `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'ۺ', + `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'ۺݵôСޣλΪֽڣ0ʾʹĬֵ', + `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'ʷ', + `gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT 'ʱ', + `gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '޸ʱ', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_tenant_id` (`tenant_id`) +) ENGINE=InnoDB AUTO_INCREMENT=461 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='⻧Ϣ'; \ No newline at end of file diff --git a/config/src/main/resources/nacos-config-logback.xml b/config/src/main/resources/nacos-config-logback.xml new file mode 100755 index 00000000000..57615dd65e3 --- /dev/null +++ b/config/src/main/resources/nacos-config-logback.xml @@ -0,0 +1,283 @@ + + + + + + ${user.home}/nacos/logs/cfg-dump.log + true + + ${user.home}/nacos/logs/cfg-dump.log.%d{yyyy-MM-dd}.%i + 2GB + 15 + 7GB + true + + + %date %level %msg%n%n + GBK + + + + ${user.home}/nacos/logs/cfg-pull.log + true + + ${user.home}/nacos/logs/cfg-pull.log.%d{yyyy-MM-dd}.%i + 20MB + 15 + 128MB + true + + + %date %level %msg%n%n + GBK + + + + ${user.home}/nacos/logs/cfg-fatal.log + true + + ${user.home}/nacos/logs/cfg-fatal.log.%d{yyyy-MM-dd}.%i + 20MB + 15 + 128MB + true + + + %date %level %msg%n%n + GBK + + + + ${user.home}/nacos/logs/cfg-memory.log + true + + ${user.home}/nacos/logs/cfg-memory.log.%d{yyyy-MM-dd}.%i + 20MB + 15 + 128MB + true + + + %date %level %msg%n%n + GBK + + + + ${user.home}/nacos/logs/cfg-pull-check.log + true + + ${user.home}/nacos/logs/cfg-pull-check.log.%d{yyyy-MM-dd}.%i + 1GB + 15 + 3GB + true + + + %msg%n + GBK + + + + + ${user.home}/nacos/logs/cfg-acl.log + true + + ${user.home}/nacos/logs/cfg-acl.log.%d{yyyy-MM-dd}.%i + 50MB + 15 + 512MB + true + + + %date %level %msg%n%n + GBK + + + + + ${user.home}/nacos/logs/cfg-client-request.log + true + + ${user.home}/nacos/logs/cfg-client-request.log.%d{yyyy-MM-dd}.%i + 2GB + 15 + 7GB + true + + + %date|%msg%n + GBK + + + + + ${user.home}/nacos/logs/cfg-sdk-request.log + true + + ${user.home}/nacos/logs/cfg-sdk-request.log.%d{yyyy-MM-dd}.%i + 1GB + 15 + 3GB + true + + + %date|%msg%n + GBK + + + + + ${user.home}/nacos/logs/cfg-trace.log + true + + ${user.home}/nacos/logs/cfg-trace.log.%d{yyyy-MM-dd}.%i + 2GB + 15 + 7GB + true + + + %date|%msg%n + GBK + + + + + ${user.home}/nacos/logs/cfg-notify.log + true + + ${user.home}/nacos/logs/cfg-notify.log.%d{yyyy-MM-dd}.%i + 1GB + 15 + 3GB + true + + + %date %level %msg%n%n + GBK + + + + + ${user.home}/nacos/logs/cfg-app.log + true + + ${user.home}/nacos/logs/cfg-app.log.%d{yyyy-MM-dd}.%i + 20MB + 15 + 128MB + true + + + %date %level %msg%n%n + GBK + + + + + ${user.home}/nacos/logs/cfg-server.log + true + + ${user.home}/nacos/logs/cfg-server.log.%d{yyyy-MM-dd}.%i + 50MB + 15 + 512MB + true + + + %date %level %msg%n%n + GBK + + + + + ${user.home}/nacos/logs/nacos.log + true + + ${user.home}/nacos/logs/nacos.log.%d{yyyy-MM-dd}.%i + 50MB + 15 + 512MB + true + + + %date %level %msg%n%n + GBK + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/src/main/resources/schema.sql b/config/src/main/resources/schema.sql new file mode 100644 index 00000000000..e5722e896a9 --- /dev/null +++ b/config/src/main/resources/schema.sql @@ -0,0 +1,161 @@ +CREATE SCHEMA nacos AUTHORIZATION nacos; + +CREATE TABLE config_info ( + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + app_name varchar(128), + content LONG VARCHAR NOT NULL, + md5 varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + src_user varchar(128) DEFAULT NULL, + src_ip varchar(20) DEFAULT NULL, + c_desc varchar(256) DEFAULT NULL, + c_use varchar(64) DEFAULT NULL, + effect varchar(64) DEFAULT NULL, + type varchar(64) DEFAULT NULL, + c_schema LONG VARCHAR DEFAULT NULL, + constraint configinfo_id_key PRIMARY KEY (id), + constraint uk_configinfo_datagrouptenant UNIQUE (data_id,group_id,tenant_id)); + +CREATE INDEX configinfo_dataid_key_idx ON config_info(data_id); +CREATE INDEX configinfo_groupid_key_idx ON config_info(group_id); +CREATE INDEX configinfo_dataid_group_key_idx ON config_info(data_id, group_id); + +CREATE TABLE his_config_info ( + id bigint NOT NULL, + nid bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + app_name varchar(128), + content LONG VARCHAR NOT NULL, + md5 varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00.000', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00.000', + src_user varchar(128), + src_ip varchar(20) DEFAULT NULL, + op_type char(10) DEFAULT NULL, + constraint hisconfiginfo_nid_key PRIMARY KEY (nid)); + +CREATE INDEX hisconfiginfo_dataid_key_idx ON his_config_info(data_id); +CREATE INDEX hisconfiginfo_gmt_create_idx ON his_config_info(gmt_create); +CREATE INDEX hisconfiginfo_gmt_modified_idx ON his_config_info(gmt_modified); + + +CREATE TABLE config_info_beta ( + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + app_name varchar(128), + content LONG VARCHAR NOT NULL, + beta_ips varchar(1024), + md5 varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + src_user varchar(128), + src_ip varchar(20) DEFAULT NULL, + constraint configinfobeta_id_key PRIMARY KEY (id), + constraint uk_configinfobeta_datagrouptenant UNIQUE (data_id,group_id,tenant_id)); + +CREATE TABLE config_info_tag ( + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + tag_id varchar(128) NOT NULL, + app_name varchar(128), + content LONG VARCHAR NOT NULL, + md5 varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + src_user varchar(128), + src_ip varchar(20) DEFAULT NULL, + constraint configinfotag_id_key PRIMARY KEY (id), + constraint uk_configinfotag_datagrouptenanttag UNIQUE (data_id,group_id,tenant_id,tag_id)); + +CREATE TABLE config_info_aggr ( + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + datum_id varchar(255) NOT NULL, + app_name varchar(128), + content LONG VARCHAR NOT NULL, + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + constraint configinfoaggr_id_key PRIMARY KEY (id), + constraint uk_configinfoaggr_datagrouptenantdatum UNIQUE (data_id,group_id,tenant_id,datum_id)); + +CREATE TABLE app_list ( + id bigint NOT NULL generated by default as identity, + app_name varchar(128) NOT NULL, + is_dynamic_collect_disabled smallint DEFAULT 0, + last_sub_info_collected_time timestamp DEFAULT '1970-01-01 08:00:00.0', + sub_info_lock_owner varchar(128), + sub_info_lock_time timestamp DEFAULT '1970-01-01 08:00:00.0', + constraint applist_id_key PRIMARY KEY (id), + constraint uk_appname UNIQUE (app_name)); + +CREATE TABLE app_configdata_relation_subs ( + id bigint NOT NULL generated by default as identity, + app_name varchar(128) NOT NULL, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', + constraint configdatarelationsubs_id_key PRIMARY KEY (id), + constraint uk_app_sub_config_datagroup UNIQUE (app_name, data_id, group_id)); + + +CREATE TABLE app_configdata_relation_pubs ( + id bigint NOT NULL generated by default as identity, + app_name varchar(128) NOT NULL, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', + constraint configdatarelationpubs_id_key PRIMARY KEY (id), + constraint uk_app_pub_config_datagroup UNIQUE (app_name, data_id, group_id)); + +CREATE TABLE config_tags_relation ( + id bigint NOT NULL, + tag_name varchar(128) NOT NULL, + tag_type varchar(64) DEFAULT NULL, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) DEFAULT '', + nid bigint NOT NULL generated by default as identity, + constraint config_tags_id_key PRIMARY KEY (nid), + constraint uk_configtagrelation_configidtag UNIQUE (id, tag_name, tag_type)); + +CREATE INDEX config_tags_tenant_id_idx ON config_tags_relation(tenant_id); + +CREATE TABLE group_capacity ( + id bigint NOT NULL generated by default as identity, + group_id varchar(128) DEFAULT '', + quota int DEFAULT 0, + usage int DEFAULT 0, + max_size int DEFAULT 0, + max_aggr_count int DEFAULT 0, + max_aggr_size int DEFAULT 0, + max_history_count int DEFAULT 0, + gmt_create timestamp DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', + constraint group_capacity_id_key PRIMARY KEY (id), + constraint uk_group_id UNIQUE (group_id)); + +CREATE TABLE tenant_capacity ( + id bigint NOT NULL generated by default as identity, + tenant_id varchar(128) DEFAULT '', + quota int DEFAULT 0, + usage int DEFAULT 0, + max_size int DEFAULT 0, + max_aggr_count int DEFAULT 0, + max_aggr_size int DEFAULT 0, + max_history_count int DEFAULT 0, + gmt_create timestamp DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', + constraint tenant_capacity_id_key PRIMARY KEY (id), + constraint uk_tenant_id UNIQUE (tenant_id)); + diff --git a/config/src/main/resources/version/version.txt b/config/src/main/resources/version/version.txt new file mode 100755 index 00000000000..17851514a7b --- /dev/null +++ b/config/src/main/resources/version/version.txt @@ -0,0 +1 @@ +${pom.version} \ No newline at end of file diff --git a/config/src/test/java/com/alibaba/nacos/config/mock/FilterConfigMock.java b/config/src/test/java/com/alibaba/nacos/config/mock/FilterConfigMock.java new file mode 100644 index 00000000000..02095a9b9d9 --- /dev/null +++ b/config/src/test/java/com/alibaba/nacos/config/mock/FilterConfigMock.java @@ -0,0 +1,56 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.mock; + +import java.util.Enumeration; + +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; + + + +public class FilterConfigMock implements FilterConfig { + + public FilterConfigMock(ServletContext context) { + this.context = context; + } + + + @Override + public String getFilterName() { + // TODO Auto-generated method stub + return null; + } + + @Override + public ServletContext getServletContext() { + return context; + } + + @Override + public String getInitParameter(String name) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Enumeration getInitParameterNames() { + // TODO Auto-generated method stub + return null; + } + + final ServletContext context; +} diff --git a/config/src/test/java/com/alibaba/nacos/config/mock/ServletContextMock.java b/config/src/test/java/com/alibaba/nacos/config/mock/ServletContextMock.java new file mode 100644 index 00000000000..736543b6591 --- /dev/null +++ b/config/src/test/java/com/alibaba/nacos/config/mock/ServletContextMock.java @@ -0,0 +1,356 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.mock; + +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Enumeration; +import java.util.EventListener; +import java.util.Map; +import java.util.Set; + +import javax.servlet.Filter; +import javax.servlet.FilterRegistration; +import javax.servlet.FilterRegistration.Dynamic; +import javax.servlet.RequestDispatcher; +import javax.servlet.Servlet; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRegistration; +import javax.servlet.SessionCookieConfig; +import javax.servlet.SessionTrackingMode; +import javax.servlet.descriptor.JspConfigDescriptor; + + + + +public class ServletContextMock implements ServletContext { + + @Override + public String getContextPath() { + // TODO Auto-generated method stub + return null; + } + + @Override + public ServletContext getContext(String uripath) { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getMajorVersion() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getMinorVersion() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public String getMimeType(String file) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Set getResourcePaths(String path) { + // TODO Auto-generated method stub + return null; + } + + @Override + public URL getResource(String path) throws MalformedURLException { + // TODO Auto-generated method stub + return null; + } + + @Override + public InputStream getResourceAsStream(String path) { + // TODO Auto-generated method stub + return null; + } + + @Override + public RequestDispatcher getRequestDispatcher(String path) { + // TODO Auto-generated method stub + return null; + } + + @Override + public RequestDispatcher getNamedDispatcher(String name) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Servlet getServlet(String name) throws ServletException { + // TODO Auto-generated method stub + return null; + } + + @Override + public Enumeration getServlets() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Enumeration getServletNames() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void log(String msg) { + // TODO Auto-generated method stub + + } + + @Override + public void log(Exception exception, String msg) { + // TODO Auto-generated method stub + + } + + @Override + public void log(String message, Throwable throwable) { + // TODO Auto-generated method stub + + } + + @Override + public String getRealPath(String path) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getServerInfo() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getInitParameter(String name) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Enumeration getInitParameterNames() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Object getAttribute(String name) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Enumeration getAttributeNames() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setAttribute(String name, Object object) { + // TODO Auto-generated method stub + + } + + @Override + public void removeAttribute(String name) { + // TODO Auto-generated method stub + + } + + @Override + public String getServletContextName() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Dynamic addFilter(String arg0, String arg1) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Dynamic addFilter(String arg0, Filter arg1) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Dynamic addFilter(String arg0, Class arg1) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void addListener(Class arg0) { + // TODO Auto-generated method stub + + } + + @Override + public void addListener(String arg0) { + // TODO Auto-generated method stub + + } + + @Override + public void addListener(T arg0) { + // TODO Auto-generated method stub + + } + + @Override + public javax.servlet.ServletRegistration.Dynamic addServlet(String arg0, String arg1) { + // TODO Auto-generated method stub + return null; + } + + @Override + public javax.servlet.ServletRegistration.Dynamic addServlet(String arg0, Servlet arg1) { + // TODO Auto-generated method stub + return null; + } + + @Override + public javax.servlet.ServletRegistration.Dynamic addServlet(String arg0, + Class arg1) { + // TODO Auto-generated method stub + return null; + } + + @Override + public T createFilter(Class arg0) throws ServletException { + // TODO Auto-generated method stub + return null; + } + + @Override + public T createListener(Class arg0) throws ServletException { + // TODO Auto-generated method stub + return null; + } + + @Override + public T createServlet(Class arg0) throws ServletException { + // TODO Auto-generated method stub + return null; + } + + @Override + public void declareRoles(String... arg0) { + // TODO Auto-generated method stub + + } + + @Override + public ClassLoader getClassLoader() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Set getDefaultSessionTrackingModes() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getEffectiveMajorVersion() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getEffectiveMinorVersion() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public Set getEffectiveSessionTrackingModes() { + // TODO Auto-generated method stub + return null; + } + + @Override + public FilterRegistration getFilterRegistration(String arg0) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Map getFilterRegistrations() { + // TODO Auto-generated method stub + return null; + } + + @Override + public JspConfigDescriptor getJspConfigDescriptor() { + // TODO Auto-generated method stub + return null; + } + + @Override + public ServletRegistration getServletRegistration(String arg0) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Map getServletRegistrations() { + // TODO Auto-generated method stub + return null; + } + + @Override + public SessionCookieConfig getSessionCookieConfig() { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean setInitParameter(String arg0, String arg1) { + // TODO Auto-generated method stub + return false; + } + + @Override + public void setSessionTrackingModes(Set arg0) + throws IllegalStateException, IllegalArgumentException { + // TODO Auto-generated method stub + } + + @Override + public String getVirtualServerName() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/config/src/test/java/com/alibaba/nacos/config/server/controller/HealthControllerUnitTest.java b/config/src/test/java/com/alibaba/nacos/config/server/controller/HealthControllerUnitTest.java new file mode 100644 index 00000000000..a31fb531094 --- /dev/null +++ b/config/src/test/java/com/alibaba/nacos/config/server/controller/HealthControllerUnitTest.java @@ -0,0 +1,67 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.controller; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.springframework.mock.web.MockServletContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.service.DataSourceService; + +/** + * Created by qingliang on 2017/8/14. + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = MockServletContext.class) +@WebAppConfiguration +public class HealthControllerUnitTest { + + + @InjectMocks + HealthController healthController; + + @Mock + DataSourceService dataSourceService; + + private MockMvc mockmvc; + @Before + public void setUp() throws Exception { + mockmvc = MockMvcBuilders.standaloneSetup(healthController).build(); + } + + @Test + public void testGetHealth() throws Exception{ + + Mockito.when(dataSourceService.getHealth()).thenReturn("UP"); + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get(Constants.HEALTH_CONTROLLER_PATH); + String actualValue = mockmvc.perform(builder).andReturn().getResponse().getContentAsString(); + Assert.assertEquals("UP", actualValue); + + } +} diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/AggrWhitelistTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/AggrWhitelistTest.java new file mode 100644 index 00000000000..fa1420be501 --- /dev/null +++ b/config/src/test/java/com/alibaba/nacos/config/server/service/AggrWhitelistTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service; + + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +import com.alibaba.nacos.config.server.service.AggrWhitelist; + +@RunWith(SpringJUnit4ClassRunner.class) +@WebAppConfiguration +public class AggrWhitelistTest { + + AggrWhitelist service; + + @Before + public void before() throws Exception { + service = new AggrWhitelist(); + } + + @Test + public void testIsAggrDataId() { + List list = new ArrayList(); + list.add("com.taobao.jiuren.*"); + list.add("NS_NACOS_SUBSCRIPTION_TOPIC_*"); + list.add("com.taobao.tae.AppListOnGrid-*"); + service.compile(list); + + assertEquals(false, service.isAggrDataId("com.abc")); + assertEquals(false, service.isAggrDataId("com.taobao.jiuren")); + assertEquals(false, service.isAggrDataId("com.taobao.jiurenABC")); + assertEquals(true, service.isAggrDataId("com.taobao.jiuren.abc")); + assertEquals(true, service.isAggrDataId("NS_NACOS_SUBSCRIPTION_TOPIC_abc")); + assertEquals(true, service.isAggrDataId("com.taobao.tae.AppListOnGrid-abc")); + } +} diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/ClientTrackServiceTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/ClientTrackServiceTest.java new file mode 100644 index 00000000000..e792df201f9 --- /dev/null +++ b/config/src/test/java/com/alibaba/nacos/config/server/service/ClientTrackServiceTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service; + +import org.junit.Assert; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +import com.alibaba.nacos.config.server.service.ClientTrackService; +import com.alibaba.nacos.config.server.service.ConfigService; +import com.alibaba.nacos.config.server.utils.GroupKey2; + + +@RunWith(SpringJUnit4ClassRunner.class) +@WebAppConfiguration +public class ClientTrackServiceTest { + + @Before + public void before() { + ClientTrackService.clientRecords.clear(); + } + + @Test + public void test_trackClientMd5() { + String clientIp = "1.1.1.1"; + String dataId = "com.taobao.session.xml"; + String group = "online"; + String groupKey = GroupKey2.getKey(dataId, group); + String md5 = "xxxxxxxxxxxxx"; + + ConfigService.updateMd5(groupKey, md5, System.currentTimeMillis()); + + ClientTrackService.trackClientMd5(clientIp, groupKey, md5); + ClientTrackService.trackClientMd5(clientIp, groupKey, md5); + + Assert.assertEquals(true, ClientTrackService.isClientUptodate(clientIp).get(groupKey)); + Assert.assertEquals(1, ClientTrackService.subscribeClientCount()); + Assert.assertEquals(1, ClientTrackService.subscriberCount()); + + //服务端数据更新 + ConfigService.updateMd5(groupKey, md5 + "111", System.currentTimeMillis()); + Assert.assertEquals(false, ClientTrackService.isClientUptodate(clientIp).get(groupKey)); + } + +} diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/DiskServiceUnitTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/DiskServiceUnitTest.java new file mode 100755 index 00000000000..93382ec8f04 --- /dev/null +++ b/config/src/test/java/com/alibaba/nacos/config/server/service/DiskServiceUnitTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.service; + +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +import com.alibaba.nacos.config.server.service.DiskUtil; + +import javax.servlet.ServletContext; + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.io.IOException; + +@RunWith(SpringJUnit4ClassRunner.class) +@WebAppConfiguration +public class DiskServiceUnitTest { + + private DiskUtil diskService; + + private ServletContext servletContext; + + private File tempFile; + + private String path; + + @Before + public void setUp() throws IOException { + this.tempFile = File.createTempFile("diskServiceTest", "tmp"); + this.path = tempFile.getParent(); + this.diskService = new DiskUtil(); + } + + @Test + public void testCreateConfig() throws IOException { + diskService.saveToDisk("testDataId", "testGroup", "testTenant", "testContent"); + String content = diskService.getConfig("testDataId", "testGroup", "testTenant"); + assertEquals(content, "testContent"); + + } + + @After + public void tearDown() throws IOException { + tempFile.delete(); + } +} diff --git a/config/src/test/java/com/alibaba/nacos/config/server/utils/GroupKeyTest.java b/config/src/test/java/com/alibaba/nacos/config/server/utils/GroupKeyTest.java new file mode 100644 index 00000000000..412d1074a01 --- /dev/null +++ b/config/src/test/java/com/alibaba/nacos/config/server/utils/GroupKeyTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +@RunWith(SpringJUnit4ClassRunner.class) +@WebAppConfiguration +public class GroupKeyTest { + + @Test + public void test_parseGroupKey_非法的() { + String key = "11111+222+333333+444"; + try { + GroupKey2.parseKey(key); + Assert.fail(); + } catch (IllegalArgumentException e) { + System.out.println(e.toString()); + } + + key = "11111+"; + try { + GroupKey2.parseKey(key); + Assert.fail(); + } catch (IllegalArgumentException e) { + System.out.println(e.toString()); + } + + key = "11111%29+222"; + try { + GroupKey2.parseKey(key); + Assert.fail(); + } catch (IllegalArgumentException e) { + System.out.println(e.toString()); + } + + key = "11111%2b+222"; + try { + GroupKey2.parseKey(key); + Assert.fail(); + } catch (IllegalArgumentException e) { + System.out.println(e.toString()); + } + + + key = "11111%25+222"; + String[] pair = GroupKey2.parseKey(key); + Assert.assertEquals("11111%", pair[0]); + Assert.assertEquals("222", pair[1]); + } +} diff --git a/config/src/test/java/com/alibaba/nacos/config/server/utils/SimpleReadWriteLockTest.java b/config/src/test/java/com/alibaba/nacos/config/server/utils/SimpleReadWriteLockTest.java new file mode 100644 index 00000000000..9c15a2c722c --- /dev/null +++ b/config/src/test/java/com/alibaba/nacos/config/server/utils/SimpleReadWriteLockTest.java @@ -0,0 +1,77 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +import com.alibaba.nacos.config.server.utils.SimpleReadWriteLock; + + +@RunWith(SpringJUnit4ClassRunner.class) +@WebAppConfiguration +public class SimpleReadWriteLockTest { + + @Test + public void test_双重读锁_全部释放_加写锁() { + SimpleReadWriteLock lock = new SimpleReadWriteLock(); + assertEquals(true, lock.tryReadLock()); + assertEquals(true, lock.tryReadLock()); + + lock.releaseReadLock(); + lock.releaseReadLock(); + + assertEquals(true, lock.tryWriteLock()); + } + + @Test + public void test_加写锁() { + SimpleReadWriteLock lock = new SimpleReadWriteLock(); + assertEquals(true, lock.tryWriteLock()); + lock.releaseWriteLock(); + } + + @Test + public void test_双重写锁() { + SimpleReadWriteLock lock = new SimpleReadWriteLock(); + + assertEquals(true, lock.tryWriteLock()); + assertEquals(false, lock.tryWriteLock()); + } + + @Test + public void test_先读锁后写锁() { + SimpleReadWriteLock lock = new SimpleReadWriteLock(); + + assertEquals(true, lock.tryReadLock()); + assertEquals(false, lock.tryWriteLock()); + } + + @Test + public void test_双重读锁_释放一个_加写锁失败() { + SimpleReadWriteLock lock = new SimpleReadWriteLock(); + assertEquals(true, lock.tryReadLock()); + assertEquals(true, lock.tryReadLock()); + + lock.releaseReadLock(); + + assertEquals(false, lock.tryWriteLock()); + } +} diff --git a/config/src/test/java/com/alibaba/nacos/config/server/utils/event/EventDispatcherTest.java b/config/src/test/java/com/alibaba/nacos/config/server/utils/event/EventDispatcherTest.java new file mode 100755 index 00000000000..c451e33fceb --- /dev/null +++ b/config/src/test/java/com/alibaba/nacos/config/server/utils/event/EventDispatcherTest.java @@ -0,0 +1,99 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.config.server.utils.event; + +import org.junit.After; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +import com.alibaba.nacos.config.server.utils.event.EventDispatcher; +import com.alibaba.nacos.config.server.utils.event.EventDispatcher.Event; +import com.alibaba.nacos.config.server.utils.event.EventDispatcher.AbstractEventListener; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +import static org.junit.Assert.assertEquals; + + +@RunWith(SpringJUnit4ClassRunner.class) +@WebAppConfiguration +public class EventDispatcherTest { + + @After + public void after() { + EventDispatcher.clear(); + } + + @Ignore + @Test + public void testAddListener() throws Exception { + final AbstractEventListener listener = new MockListener(); + + int vusers = 1000; + final CountDownLatch latch = new CountDownLatch(vusers); + + for (int i = 0; i < vusers; ++i) { + new Thread(new Runnable() { + public void run() { + latch.countDown(); + EventDispatcher.addEventListener(listener); + } + }).start(); + } + + latch.await(); + assertEquals(1, EventDispatcher.LISTENER_HUB.size()); + } + + @Test + public void testFireEvent() { + EventDispatcher.fireEvent(new MockEvent()); + assertEquals(0, MockListener.count); + + EventDispatcher.addEventListener(new MockListener()); + + EventDispatcher.fireEvent(new MockEvent()); + assertEquals(1, MockListener.count); + + EventDispatcher.fireEvent(new MockEvent()); + assertEquals(2, MockListener.count); + } +} + + +class MockEvent implements Event { +} + +class MockListener extends AbstractEventListener { + static int count = 0; + + @Override + public List> interest() { + List> types = new ArrayList>(); + types.add(MockEvent.class); + return types; + } + + @Override + public void onEvent(Event event) { + ++count; + } +} diff --git a/config/src/test/resources/application.xml-bk b/config/src/test/resources/application.xml-bk new file mode 100644 index 00000000000..e69de29bb2d diff --git a/config/src/test/resources/application.xml-test4derby b/config/src/test/resources/application.xml-test4derby new file mode 100644 index 00000000000..e69de29bb2d diff --git a/config/src/test/resources/jdbc.properties b/config/src/test/resources/jdbc.properties new file mode 100755 index 00000000000..e69de29bb2d diff --git a/config/src/test/resources/log4j.properties b/config/src/test/resources/log4j.properties new file mode 100644 index 00000000000..a6e39c43eef --- /dev/null +++ b/config/src/test/resources/log4j.properties @@ -0,0 +1,20 @@ +log4j.rootLogger=DEBUG, ServerDailyRollingFile,stdout +log4j.appender.ServerDailyRollingFile=org.apache.log4j.DailyRollingFileAppender +log4j.appender.ServerDailyRollingFile.DatePattern='.'yyyy-MM-dd_HH +log4j.appender.ServerDailyRollingFile.File=${webapp.root}/WEB-INF/logs/nacos-server.log +log4j.appender.ServerDailyRollingFile.layout=org.apache.log4j.PatternLayout +log4j.appender.ServerDailyRollingFile.layout.ConversionPattern=[%p] [%t] %d{MM-dd HH:mm:ss,SSS} [%c{1}] - %m%n +log4j.appender.ServerDailyRollingFile.Append=true + +log4j.logger.opLog=INFO, opFile +log4j.appender.opFile=org.apache.log4j.DailyRollingFileAppender +log4j.appender.opFile.DatePattern='.'yyyy-MM-dd_HH +log4j.appender.opFile.File=${webapp.root}/WEB-INF/logs/operation.log +log4j.appender.opFile.layout=org.apache.log4j.PatternLayout +log4j.appender.opFile.layout.ConversionPattern=[%p] [%t] %d{MM-dd HH:mm:ss,SSS} [%c{1}] - %m%n +log4j.appender.opFile.Append=true + +log4j.logger.com.taobao.config = warn +log4j.logger.org.apache.http.wire=warn +log4j.logger.java.sql = warn +log4j.logger.com.ibatis.common.jdbc=warn \ No newline at end of file diff --git a/config/src/test/resources/user.properties b/config/src/test/resources/user.properties new file mode 100755 index 00000000000..ab26fdd51bb --- /dev/null +++ b/config/src/test/resources/user.properties @@ -0,0 +1 @@ +admin=admin \ No newline at end of file diff --git a/console/pom.xml b/console/pom.xml new file mode 100644 index 00000000000..54996995701 --- /dev/null +++ b/console/pom.xml @@ -0,0 +1,71 @@ + + 4.0.0 + + com.alibaba.nacos + nacos-all + 0.1.0 + + nacos-console + + jar + nacos-console ${project.version} + http://maven.apache.org + + UTF-8 + + + + + ${project.groupId} + nacos-config + + + org.apache.tomcat.embed + tomcat-embed-jasper + 7.0.59 + + + ${project.groupId} + nacos-naming + + + + + org.slf4j + log4j-over-slf4j + + + + org.slf4j + jcl-over-slf4j + + + + org.slf4j + jul-to-slf4j + + + + + + nacos-server + + + org.springframework.boot + spring-boot-maven-plugin + + com.alibaba.nacos.Nacos + + + + + repackage + + + + + + + diff --git a/console/src/main/java/com/alibaba/nacos/Nacos.java b/console/src/main/java/com/alibaba/nacos/Nacos.java new file mode 100644 index 00000000000..4142ed54cc9 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/Nacos.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos; + +import java.net.UnknownHostException; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.servlet.ServletComponentScan; + +/** + * @author nacos + */ +@SpringBootApplication(scanBasePackages = "com.alibaba.nacos") +@ServletComponentScan +public class Nacos { + + public static void main(String[] args) throws UnknownHostException { + SpringApplication.run(Nacos.class, args); + } +} diff --git a/console/src/main/resources/application.properties b/console/src/main/resources/application.properties new file mode 100644 index 00000000000..776d2928ba6 --- /dev/null +++ b/console/src/main/resources/application.properties @@ -0,0 +1,47 @@ +# spring +management.security.enabled=false +server.contextPath=/nacos +server.servlet.contextPath=/nacos +server.port=8080 +spring.mvc.view.prefix=/jsp/ +# 响应页面默认后缀 +spring.mvc.view.suffix=.jsp +spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration +#logging.level.root=DEBUG + +# P0 key,For Debug. whether use address-server; true:use; false:not use;default:true +useAddressServer=true + +# whether open interInterFaceFilter; true:open; false:close; if open, others can't call inner interface. default:false +openInnerInterfaceFilter=false + +# quickStart stip dumpAll;only dump change config +isQuickStart=false + +# server notify each otherd +notifyConnectTimeout=200 + +# server notify each other +notifySocketTimeout=8000 + +# whether health check +isHealthCheck=true + +# health check max fail count +maxHealthCheckFailCount=12 + +# whether open spas; true:open; false:close +OPEN_SPAS=true + + +db.num=2 +db.url.0=jdbc:mysql://11.162.196.161:3306/diamond_devtest?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true +db.url.1=jdbc:mysql://11.163.152.91:3306/diamond_devtest?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true +db.user=diamond_devtest +db.password=4b9622f3f70c7677835ac5a6719e7caf + + + + +enableAccessControl=false + diff --git a/console/src/main/resources/banner.txt b/console/src/main/resources/banner.txt new file mode 100644 index 00000000000..4ebabe6fa06 --- /dev/null +++ b/console/src/main/resources/banner.txt @@ -0,0 +1,15 @@ + + ,--. + ,--.'| + ,--,: : | +,`--.'`| ' : ,---. +| : : | | ' ,'\ .--.--. +: | \ | : ,--.--. ,---. / / | / / ' +| : ' '; | / \ / \. ; ,. :| : /`./ +' ' ;. ;.--. .-. | / / '' | |: :| : ;_ +| | | \ | \__\/: . .. ' / ' | .; : \ \ `. +' : | ; .' ," .--.; |' ; :__| : | `----. \ +| | '`--' / / ,. |' | '.'|\ \ / / /`--' / +' : | ; : .' \ : : `----' '--'. / +; |.' | , .-./\ \ / `--'---' +'---' `--`---' `----' diff --git a/console/src/main/resources/diamond-server-logback.xml b/console/src/main/resources/diamond-server-logback.xml new file mode 100755 index 00000000000..060b7fb2a8a --- /dev/null +++ b/console/src/main/resources/diamond-server-logback.xml @@ -0,0 +1,261 @@ + + + + + + ${user.home}/diamond/logs/dump.log + true + + ${user.home}/diamond/logs/dump.log.%d{yyyy-MM-dd}.%i + 2GB + 15 + 7GB + true + + + %date %level %msg%n%n + GBK + + + + ${user.home}/diamond/logs/pull.log + true + + ${user.home}/diamond/logs/pull.log.%d{yyyy-MM-dd}.%i + 20MB + 15 + 128MB + true + + + %date %level %msg%n%n + GBK + + + + ${user.home}/diamond/logs/fatal.log + true + + ${user.home}/diamond/logs/fatal.log.%d{yyyy-MM-dd}.%i + 20MB + 15 + 128MB + true + + + %date %level %msg%n%n + GBK + + + + ${user.home}/diamond/logs/memory.log + true + + ${user.home}/diamond/logs/memory.log.%d{yyyy-MM-dd}.%i + 20MB + 15 + 128MB + true + + + %date %level %msg%n%n + GBK + + + + ${user.home}/diamond/logs/pull-check.log + true + + ${user.home}/diamond/logs/pull-check.log.%d{yyyy-MM-dd}.%i + 1GB + 15 + 3GB + true + + + %msg%n + GBK + + + + + ${user.home}/diamond/logs/acl.log + true + + ${user.home}/diamond/logs/acl.log.%d{yyyy-MM-dd}.%i + 50MB + 15 + 512MB + true + + + %date %level %msg%n%n + GBK + + + + + ${user.home}/diamond/logs/client-request.log + true + + ${user.home}/diamond/logs/client-request.log.%d{yyyy-MM-dd}.%i + 2GB + 15 + 7GB + true + + + %date|%msg%n + GBK + + + + + ${user.home}/diamond/logs/sdk-request.log + true + + ${user.home}/diamond/logs/sdk-request.log.%d{yyyy-MM-dd}.%i + 1GB + 15 + 3GB + true + + + %date|%msg%n + GBK + + + + + ${user.home}/diamond/logs/trace.log + true + + ${user.home}/diamond/logs/trace.log.%d{yyyy-MM-dd}.%i + 2GB + 15 + 7GB + true + + + %date|%msg%n + GBK + + + + + ${user.home}/diamond/logs/notify.log + true + + ${user.home}/diamond/logs/notify.log.%d{yyyy-MM-dd}.%i + 1GB + 15 + 3GB + true + + + %date %level %msg%n%n + GBK + + + + + ${user.home}/diamond/logs/app.log + true + + ${user.home}/diamond/logs/app.log.%d{yyyy-MM-dd}.%i + 20MB + 15 + 128MB + true + + + %date %level %msg%n%n + GBK + + + + + ${user.home}/diamond/logs/diamondServer.log + true + + ${user.home}/diamond/logs/diamondServer.log.%d{yyyy-MM-dd}.%i + 50MB + 15 + 512MB + true + + + %date %level %msg%n%n + GBK + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/console/src/main/resources/schema.sql b/console/src/main/resources/schema.sql new file mode 100644 index 00000000000..ae4ae291bcd --- /dev/null +++ b/console/src/main/resources/schema.sql @@ -0,0 +1,161 @@ +CREATE SCHEMA diamond AUTHORIZATION diamond; + +CREATE TABLE config_info ( + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + app_name varchar(128), + content LONG VARCHAR NOT NULL, + md5 varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + src_user varchar(128) DEFAULT NULL, + src_ip varchar(20) DEFAULT NULL, + c_desc varchar(256) DEFAULT NULL, + c_use varchar(64) DEFAULT NULL, + effect varchar(64) DEFAULT NULL, + type varchar(64) DEFAULT NULL, + c_schema LONG VARCHAR DEFAULT NULL, + constraint configinfo_id_key PRIMARY KEY (id), + constraint uk_configinfo_datagrouptenant UNIQUE (data_id,group_id,tenant_id)); + +CREATE INDEX configinfo_dataid_key_idx ON config_info(data_id); +CREATE INDEX configinfo_groupid_key_idx ON config_info(group_id); +CREATE INDEX configinfo_dataid_group_key_idx ON config_info(data_id, group_id); + +CREATE TABLE his_config_info ( + id bigint NOT NULL, + nid bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + app_name varchar(128), + content LONG VARCHAR NOT NULL, + md5 varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00.000', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00.000', + src_user varchar(128), + src_ip varchar(20) DEFAULT NULL, + op_type char(10) DEFAULT NULL, + constraint hisconfiginfo_nid_key PRIMARY KEY (nid)); + +CREATE INDEX hisconfiginfo_dataid_key_idx ON his_config_info(data_id); +CREATE INDEX hisconfiginfo_gmt_create_idx ON his_config_info(gmt_create); +CREATE INDEX hisconfiginfo_gmt_modified_idx ON his_config_info(gmt_modified); + + +CREATE TABLE config_info_beta ( + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + app_name varchar(128), + content LONG VARCHAR NOT NULL, + beta_ips varchar(1024), + md5 varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + src_user varchar(128), + src_ip varchar(20) DEFAULT NULL, + constraint configinfobeta_id_key PRIMARY KEY (id), + constraint uk_configinfobeta_datagrouptenant UNIQUE (data_id,group_id,tenant_id)); + +CREATE TABLE config_info_tag ( + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + tag_id varchar(128) NOT NULL, + app_name varchar(128), + content LONG VARCHAR NOT NULL, + md5 varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + src_user varchar(128), + src_ip varchar(20) DEFAULT NULL, + constraint configinfotag_id_key PRIMARY KEY (id), + constraint uk_configinfotag_datagrouptenanttag UNIQUE (data_id,group_id,tenant_id,tag_id)); + +CREATE TABLE config_info_aggr ( + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + datum_id varchar(255) NOT NULL, + app_name varchar(128), + content LONG VARCHAR NOT NULL, + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + constraint configinfoaggr_id_key PRIMARY KEY (id), + constraint uk_configinfoaggr_datagrouptenantdatum UNIQUE (data_id,group_id,tenant_id,datum_id)); + +CREATE TABLE app_list ( + id bigint NOT NULL generated by default as identity, + app_name varchar(128) NOT NULL, + is_dynamic_collect_disabled smallint DEFAULT 0, + last_sub_info_collected_time timestamp DEFAULT '1970-01-01 08:00:00.0', + sub_info_lock_owner varchar(128), + sub_info_lock_time timestamp DEFAULT '1970-01-01 08:00:00.0', + constraint applist_id_key PRIMARY KEY (id), + constraint uk_appname UNIQUE (app_name)); + +CREATE TABLE app_configdata_relation_subs ( + id bigint NOT NULL generated by default as identity, + app_name varchar(128) NOT NULL, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', + constraint configdatarelationsubs_id_key PRIMARY KEY (id), + constraint uk_app_sub_config_datagroup UNIQUE (app_name, data_id, group_id)); + + +CREATE TABLE app_configdata_relation_pubs ( + id bigint NOT NULL generated by default as identity, + app_name varchar(128) NOT NULL, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', + constraint configdatarelationpubs_id_key PRIMARY KEY (id), + constraint uk_app_pub_config_datagroup UNIQUE (app_name, data_id, group_id)); + +CREATE TABLE config_tags_relation ( + id bigint NOT NULL, + tag_name varchar(128) NOT NULL, + tag_type varchar(64) DEFAULT NULL, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) DEFAULT '', + nid bigint NOT NULL generated by default as identity, + constraint config_tags_id_key PRIMARY KEY (nid), + constraint uk_configtagrelation_configidtag UNIQUE (id, tag_name, tag_type)); + +CREATE INDEX config_tags_tenant_id_idx ON config_tags_relation(tenant_id); + +CREATE TABLE group_capacity ( + id bigint NOT NULL generated by default as identity, + group_id varchar(128) DEFAULT '', + quota int DEFAULT 0, + usage int DEFAULT 0, + max_size int DEFAULT 0, + max_aggr_count int DEFAULT 0, + max_aggr_size int DEFAULT 0, + max_history_count int DEFAULT 0, + gmt_create timestamp DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', + constraint group_capacity_id_key PRIMARY KEY (id), + constraint uk_group_id UNIQUE (group_id)); + +CREATE TABLE tenant_capacity ( + id bigint NOT NULL generated by default as identity, + tenant_id varchar(128) DEFAULT '', + quota int DEFAULT 0, + usage int DEFAULT 0, + max_size int DEFAULT 0, + max_aggr_count int DEFAULT 0, + max_aggr_size int DEFAULT 0, + max_history_count int DEFAULT 0, + gmt_create timestamp DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', + constraint tenant_capacity_id_key PRIMARY KEY (id), + constraint uk_tenant_id UNIQUE (tenant_id)); + diff --git a/core/pom.xml b/core/pom.xml new file mode 100644 index 00000000000..f20157583c1 --- /dev/null +++ b/core/pom.xml @@ -0,0 +1,35 @@ + + + + com.alibaba.nacos + nacos-all + 0.1.0 + + + 4.0.0 + + nacos-core + jar + + nacos-core ${project.version} + http://maven.apache.org + + + UTF-8 + + + + + junit + junit + 3.8.1 + test + + + ${project.groupId} + nacos-common + + + diff --git a/core/src/main/java/com/alibaba/nacos/core/App.java b/core/src/main/java/com/alibaba/nacos/core/App.java new file mode 100644 index 00000000000..b006320b4a8 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/App.java @@ -0,0 +1,28 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.core; + +/** + * Hello world! + * @author xxc + */ +public class App +{ + public static void main( String[] args ) + { + System.out.println( "Hello World!" ); + } +} diff --git a/core/src/test/java/com/alibaba/nacos/core/AppTest.java b/core/src/test/java/com/alibaba/nacos/core/AppTest.java new file mode 100644 index 00000000000..60c39f223d6 --- /dev/null +++ b/core/src/test/java/com/alibaba/nacos/core/AppTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.core; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Unit test for simple App. + */ +public class AppTest + extends TestCase +{ + /** + * Create the test case + * + * @param testName name of the test case + */ + public AppTest( String testName ) + { + super( testName ); + } + + /** + * @return the suite of tests being tested + */ + public static Test suite() + { + return new TestSuite( AppTest.class ); + } + + /** + * Rigourous Test :-) + */ + public void testApp() + { + assertTrue( true ); + } +} diff --git a/distribution/LICENSE-BIN b/distribution/LICENSE-BIN new file mode 100644 index 00000000000..372617233b3 --- /dev/null +++ b/distribution/LICENSE-BIN @@ -0,0 +1,334 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (properties) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. + +------ +This product has a bundle logback, which is available under the EPL v1.0 License. +The source code of logback can be found at https://github.com/qos-ch/logback. + +Logback LICENSE +--------------- + +Logback: the reliable, generic, fast and flexible logging framework. +Copyright (C) 1999-2015, QOS.ch. All rights reserved. + +This program and the accompanying materials are dual-licensed under +either the terms of the Eclipse Public License v1.0 as published by +the Eclipse Foundation + + or (per the licensee's choosing) + +under the terms of the GNU Lesser General Public License version 2.1 +as published by the Free Software Foundation. + +------ +This product has a bundle slf4j, which is available under the MIT License. +The source code of slf4j can be found at https://github.com/qos-ch/slf4j. + + Copyright (c) 2004-2017 QOS.ch + All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +------ +This product has a bundle fastjson, which is available under the ASL2 License. +The source code of fastjson can be found at https://github.com/alibaba/fastjson. + + Copyright 1999-2016 Alibaba Group Holding Ltd. + + Licensed 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. + +------ +This product has a bundle javassist, which is available under the ASL2 License. +The source code of javassist can be found at https://github.com/jboss-javassist/javassist. + + Copyright (C) 1999- by Shigeru Chiba, All rights reserved. + + Javassist (JAVA programming ASSISTant) makes Java bytecode manipulation simple. + It is a class library for editing bytecodes in Java; it enables Java programs to define a new class + at runtime and to modify a class file when the JVM loads it. Unlike other similar bytecode editors, + Javassist provides two levels of API: source level and bytecode level. If the users use the source- level API, + they can edit a class file without knowledge of the specifications of the Java bytecode. + The whole API is designed with only the vocabulary of the Java language. + You can even specify inserted bytecode in the form of source text; Javassist compiles it on the fly. + On the other hand, the bytecode-level API allows the users to directly edit a class file as other editors. + + This software is distributed under the Mozilla Public License Version 1.1, + the GNU Lesser General Public License Version 2.1 or later, or the Apache License Version 2.0. + +------ +This product has a bundle jna, which is available under the ASL2 License. +The source code of jna can be found at https://github.com/java-native-access/jna. + + This copy of JNA is licensed under the + Apache (Software) License, version 2.0 ("the License"). + See the License for details about distribution rights, and the + specific rights regarding derivate works. + + You may obtain a copy of the License at: + + http://www.apache.org/licenses/ + + A copy is also included in the downloadable source code package + containing JNA, in file "AL2.0", under the same directory + as this file. +------ +This product has a bundle guava, which is available under the ASL2 License. +The source code of guava can be found at https://github.com/google/guava. + + Copyright (C) 2007 The Guava authors + + Licensed 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. +------ +This product has a bundle OpenMessaging, which is available under the ASL2 License. +The source code of OpenMessaging can be found at https://github.com/openmessaging/openmessaging. + + Copyright (C) 2017 The OpenMessaging authors. + + Licensed 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. + diff --git a/distribution/NOTICE-BIN b/distribution/NOTICE-BIN new file mode 100644 index 00000000000..3a2af57271d --- /dev/null +++ b/distribution/NOTICE-BIN @@ -0,0 +1,36 @@ +Nacos +Copyright 2018-2019 The Apache Software Foundation + +This product includes software developed at +The Alibaba MiddleWare Group. + +------ +This product has a bundle netty: + The Spring oot Project + ================= + +Please visit the Netty web site for more information: + + * http://netty.io/ + +Copyright 2014 The Netty Project + +The Netty Project 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. + +Also, please refer to each LICENSE..txt file, which is located in +the 'license' directory of the distribution file, for the license terms of the +components that this product depends on. + +------ +This product has a bundle commons-lang, which includes software from the Spring Framework, +under the Apache License 2.0 (see: StringUtils.containsWhitespace()) \ No newline at end of file diff --git a/distribution/bin/deploy.sh b/distribution/bin/deploy.sh new file mode 100644 index 00000000000..156c9a4f0a7 --- /dev/null +++ b/distribution/bin/deploy.sh @@ -0,0 +1,4 @@ +sudo -u admin rm -rf ../target/nacos-console-0.1.0.jar +sudo -u admin scp xingxuechao@$1:/Users/xingxuechao/Documents/source/gitlab/opensource/nacos/console/target/nacos-console-0.1.0.jar /home/admin/nacos/target +sudo -u admin sh shutdown.sh +sudo -u admin sh startup.sh \ No newline at end of file diff --git a/distribution/bin/deploy_naming.sh b/distribution/bin/deploy_naming.sh new file mode 100644 index 00000000000..58c83359a64 --- /dev/null +++ b/distribution/bin/deploy_naming.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +cd ../../naming + +mvn clean install -Dmaven.test.skip=true;osscmd upload target/nacos-naming-0.1.0.jar oss://gns-upload/nacos-naming-0.1.0.jar \ No newline at end of file diff --git a/distribution/bin/run_naming.sh b/distribution/bin/run_naming.sh new file mode 100644 index 00000000000..da13456d45f --- /dev/null +++ b/distribution/bin/run_naming.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +mkdir -p ~/conf + +PID=`ps -ef | grep naming | grep -v grep | awk '{print $2}'` + +kill -9 $PID + +rm -f nacos-naming-0.1.0.jar + +wget http://gns-upload.cn-hangzhou.oss-cdn.aliyun-inc.com/nacos-naming-0.1.0.jar + +/opt/taobao/java/bin/java -Dcom.alibaba.nacos.naming.server.port=7001 -jar nacos-naming-0.1.0.jar + + +kill -9 `ps -ef | grep naming | grep -v grep | awk '{print $2}'`;rm -f nacos-naming-0.1.0.jar;wget http://gns-upload.cn-hangzhou.oss-cdn.aliyun-inc.com/nacos-naming-0.1.0.jar;/opt/taobao/java/bin/java -Dcom.alibaba.nacos.naming.server.port=7001 -jar nacos-naming-0.1.0.jar \ No newline at end of file diff --git a/distribution/bin/shutdown.cmd b/distribution/bin/shutdown.cmd new file mode 100755 index 00000000000..e80755c6592 --- /dev/null +++ b/distribution/bin/shutdown.cmd @@ -0,0 +1,12 @@ +@echo off +if not exist "%JAVA_HOME%\bin\jps.exe" echo Please set the JAVA_HOME variable in your environment, We need java(x64)! jdk8 or later is better! & EXIT /B 1 + +setlocal + +set "PATH=%JAVA_HOME%\bin;%PATH%" + +echo killing nacos server + +for /f "tokens=1" %%i in ('jps -m ^| find "nacos"') do ( taskkill /F /PID %%i ) + +echo Done! diff --git a/distribution/bin/shutdown.sh b/distribution/bin/shutdown.sh new file mode 100644 index 00000000000..d1f253bb70e --- /dev/null +++ b/distribution/bin/shutdown.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +pid=`ps ax | grep -i 'nacos' |grep java | grep -v grep | awk '{print $1}'` +if [ -z "$pid" ] ; then + echo "No nacosServer running." + exit -1; +fi + +echo "The nacosServer(${pid}) is running..." + +kill ${pid} + +echo "Send shutdown request to nacosServer(${pid}) OK" \ No newline at end of file diff --git a/distribution/bin/startup.cmd b/distribution/bin/startup.cmd new file mode 100755 index 00000000000..af9edd8b78e --- /dev/null +++ b/distribution/bin/startup.cmd @@ -0,0 +1,28 @@ +@echo off + +if not exist "%JAVA_HOME%\bin\java.exe" echo Please set the JAVA_HOME variable in your environment, We need java(x64)! jdk8 or later is better! & EXIT /B 1 +set "JAVA=%JAVA_HOME%\bin\java.exe" + +setlocal + +set BASE_DIR=%~dp0 +set BASE_DIR=%BASE_DIR:~0,-1% +for %%d in (%BASE_DIR%) do set BASE_DIR=%%~dpd + +set CLASSPATH=.;%BASE_DIR%conf;%CLASSPATH% + +set "JAVA_OPT=%JAVA_OPT% -server -Xms2g -Xmx2g -Xmn1g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m" +set "JAVA_OPT=%JAVA_OPT% -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+CMSClassUnloadingEnabled -XX:SurvivorRatio=8 -XX:-UseParNewGC" +set "JAVA_OPT=%JAVA_OPT% -verbose:gc -Xloggc:"%USERPROFILE%\rmq_srv_gc.log" -XX:+PrintGCDetails" +set "JAVA_OPT=%JAVA_OPT% -Dnacos.home=%BASE_DIR%" + +if not ""%2"" == "cluster" +set "JAVA_OPT=%JAVA_OPT% -Dnacos.standalone=true" + +set "JAVA_OPT=%JAVA_OPT% -XX:-OmitStackTraceInFastThrow" +set "JAVA_OPT=%JAVA_OPT% -XX:-UseLargePages" +set "JAVA_OPT=%JAVA_OPT% -jar %BASE_DIR%\target\nacos-server.jar" +set "JAVA_OPT=%JAVA_OPT% -cp "%CLASSPATH%"" +set CMD_LINE_ARGS= + +call "%JAVA%" %JAVA_OPT% %* \ No newline at end of file diff --git a/distribution/bin/startup.sh b/distribution/bin/startup.sh new file mode 100644 index 00000000000..bf5fb4b735a --- /dev/null +++ b/distribution/bin/startup.sh @@ -0,0 +1,57 @@ +#!/bin/sh + +error_exit () +{ + echo "ERROR: $1 !!" + exit 1 +} + +[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=$HOME/jdk/java +[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=/usr/java +[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=/opt/taobao/java +[ ! -e "$JAVA_HOME/bin/java" ] && error_exit "Please set the JAVA_HOME variable in your environment, We need java(x64)! jdk8 or later is better!" + +export MODE="cluster" +while getopts ":m:" opt +do + case $opt in + m) + MODE=$OPTARG + ;; + ?) + echo "未知参数" + exit 1;; + esac +done + +export JAVA_HOME +export JAVA="$JAVA_HOME/bin/java" +export BASE_DIR=`cd $(dirname $0)/..; pwd` +export CLASSPATH=.:${BASE_DIR}/conf:${CLASSPATH} + +#=========================================================================================== +# JVM Configuration +#=========================================================================================== +JAVA_OPT="${JAVA_OPT} -server -Xms2g -Xmx2g -Xmn1g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m" +JAVA_OPT="${JAVA_OPT} -Xdebug -Xrunjdwp:transport=dt_socket,address=9555,server=y,suspend=n" +JAVA_OPT="${JAVA_OPT} -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+CMSClassUnloadingEnabled -XX:SurvivorRatio=8 -XX:-UseParNewGC" +JAVA_OPT="${JAVA_OPT} -verbose:gc -Xloggc:${BASE_DIR}/logs/nacos_gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintAdaptiveSizePolicy" +JAVA_OPT="${JAVA_OPT} -Dnacos.home=${BASE_DIR}" +if [[ "${MODE}" == "standalone" ]]; then + JAVA_OPT="${JAVA_OPT} -Dnacos.standalone=true" +fi +JAVA_OPT="${JAVA_OPT} -XX:-OmitStackTraceInFastThrow" +JAVA_OPT="${JAVA_OPT} -XX:-UseLargePages" +JAVA_OPT="${JAVA_OPT} -jar ${BASE_DIR}/target/nacos-server.jar" +JAVA_OPT="${JAVA_OPT} ${JAVA_OPT_EXT}" +JAVA_OPT="${JAVA_OPT} -cp ${CLASSPATH}" +if [ ! -d "${BASE_DIR}/logs" ]; then + mkdir ${BASE_DIR}/logs +fi +if [ ! -f "${BASE_DIR}/logs/start.log" ]; then + touch "${BASE_DIR}/logs/start.log" +fi + + +nohup $JAVA ${JAVA_OPT} > ${BASE_DIR}/logs/start.log 2>&1 & +echo "nacos is starting,you can check the ${BASE_DIR}/logs/start.log" \ No newline at end of file diff --git a/distribution/conf/application.properties b/distribution/conf/application.properties new file mode 100644 index 00000000000..b8be02c3353 --- /dev/null +++ b/distribution/conf/application.properties @@ -0,0 +1,12 @@ +# spring +management.security.enabled=false +server.contextPath=/nacos +server.servlet.contextPath=/nacos +server.port=8080 + + +#db.num=2 +#db.url.0=jdbc:mysql://11.162.196.16:3306/nacos_devtest?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true +#db.url.1=jdbc:mysql://11.163.152.9:3306/nacos_devtest?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true +#db.user=nacos_devtest +#db.password=youdontknow \ No newline at end of file diff --git a/distribution/conf/application.properties.example b/distribution/conf/application.properties.example new file mode 100644 index 00000000000..01312f4b606 --- /dev/null +++ b/distribution/conf/application.properties.example @@ -0,0 +1,12 @@ +# spring +management.security.enabled=false +server.contextPath=/nacos +server.servlet.contextPath=/nacos +server.port=8080 + + +db.num=2 +db.url.0=jdbc:mysql://11.162.196.16:3306/nacos_devtest?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true +db.url.1=jdbc:mysql://11.163.152.9:3306/nacos_devtest?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true +db.user=nacos_devtest +db.password=nacos \ No newline at end of file diff --git a/distribution/conf/cluster.conf.example b/distribution/conf/cluster.conf.example new file mode 100644 index 00000000000..38eed31ada1 --- /dev/null +++ b/distribution/conf/cluster.conf.example @@ -0,0 +1,5 @@ +#it is ip +#example +10.10.109.214 +11.16.128.34 +11.16.128.36 \ No newline at end of file diff --git a/distribution/conf/nacos-logback.xml b/distribution/conf/nacos-logback.xml new file mode 100644 index 00000000000..0ec48ff5b5a --- /dev/null +++ b/distribution/conf/nacos-logback.xml @@ -0,0 +1,505 @@ + + + + + + ${nacos.home}/logs/naming-server.log + true + + ${nacos.home}/logs/naming-server.log.%d{yyyy-MM-dd}.%i + 2GB + 15 + 7GB + true + + + %date %level %msg%n%n + UTF-8 + + + + ${nacos.home}/logs/naming-raft.log + true + + ${nacos.home}/logs/naming-raft.log.%d{yyyy-MM-dd}.%i + 20MB + 15 + 128MB + true + + + %date %level %msg%n%n + UTF-8 + + + + ${nacos.home}/logs/naming-event.log + true + + ${nacos.home}/logs/naming-event.log.%d{yyyy-MM-dd}.%i + 20MB + 15 + 128MB + true + + + %date %level %msg%n%n + UTF-8 + + + + ${nacos.home}/logs/naming-push.log + true + + ${nacos.home}/logs/naming-push.log.%d{yyyy-MM-dd}.%i + 20MB + 15 + 128MB + true + + + %date %level %msg%n%n + UTF-8 + + + + ${nacos.home}/logs/naming-rt.log + true + + ${nacos.home}/logs/naming-rt.log.%d{yyyy-MM-dd}.%i + 1GB + 15 + 3GB + true + + + %msg%n + UTF-8 + + + + + ${nacos.home}/logs/naming-performance.log + true + + ${nacos.home}/logs/naming-performance.log.%d{yyyy-MM-dd}.%i + 50MB + 15 + 512MB + true + + + %date %level %msg%n%n + UTF-8 + + + + + ${nacos.home}/logs/naming-router.log + true + + ${nacos.home}/logs/naming-router.log.%d{yyyy-MM-dd}.%i + 2GB + 15 + 7GB + true + + + %date|%msg%n + UTF-8 + + + + + ${nacos.home}/logs/naming-cache.log + true + + ${nacos.home}/logs/naming-cache.log.%d{yyyy-MM-dd}.%i + 1GB + 15 + 3GB + true + + + %date|%msg%n + UTF-8 + + + + + ${nacos.home}/logs/naming-device.log + true + + ${nacos.home}/logs/naming-device.log.%d{yyyy-MM-dd}.%i + 2GB + 15 + 7GB + true + + + %date|%msg%n + UTF-8 + + + + + ${nacos.home}/logs/naming-tag.log + true + + ${nacos.home}/logs/naming-tag.log.%d{yyyy-MM-dd}.%i + 1GB + 15 + 3GB + true + + + %date %level %msg%n%n + UTF-8 + + + + + ${nacos.home}/logs/naming-debug.log + true + + ${nacos.home}/logs/naming-debug.log.%d{yyyy-MM-dd}.%i + 20MB + 15 + 128MB + true + + + %date %level %msg%n%n + UTF-8 + + + + + + + ${nacos.home}/logs/cfg-dump.log + true + + ${nacos.home}/logs/cfg-dump.log.%d{yyyy-MM-dd}.%i + 2GB + 15 + 7GB + true + + + %date %level %msg%n%n + GBK + + + + ${nacos.home}/logs/cfg-pull.log + true + + ${nacos.home}/logs/cfg-pull.log.%d{yyyy-MM-dd}.%i + 20MB + 15 + 128MB + true + + + %date %level %msg%n%n + GBK + + + + ${nacos.home}/logs/cfg-fatal.log + true + + ${nacos.home}/logs/cfg-fatal.log.%d{yyyy-MM-dd}.%i + 20MB + 15 + 128MB + true + + + %date %level %msg%n%n + GBK + + + + ${nacos.home}/logs/cfg-memory.log + true + + ${nacos.home}/logs/cfg-memory.log.%d{yyyy-MM-dd}.%i + 20MB + 15 + 128MB + true + + + %date %level %msg%n%n + GBK + + + + ${nacos.home}/logs/cfg-pull-check.log + true + + ${nacos.home}/logs/cfg-pull-check.log.%d{yyyy-MM-dd}.%i + 1GB + 15 + 3GB + true + + + %msg%n + GBK + + + + + ${nacos.home}/logs/cfg-acl.log + true + + ${nacos.home}/logs/cfg-acl.log.%d{yyyy-MM-dd}.%i + 50MB + 15 + 512MB + true + + + %date %level %msg%n%n + GBK + + + + + ${nacos.home}/logs/cfg-client-request.log + true + + ${nacos.home}/logs/cfg-client-request.log.%d{yyyy-MM-dd}.%i + 2GB + 15 + 7GB + true + + + %date|%msg%n + GBK + + + + + ${nacos.home}/logs/cfg-sdk-request.log + true + + ${nacos.home}/logs/cfg-sdk-request.log.%d{yyyy-MM-dd}.%i + 1GB + 15 + 3GB + true + + + %date|%msg%n + GBK + + + + + ${nacos.home}/logs/cfg-trace.log + true + + ${nacos.home}/logs/cfg-trace.log.%d{yyyy-MM-dd}.%i + 2GB + 15 + 7GB + true + + + %date|%msg%n + GBK + + + + + ${nacos.home}/logs/cfg-notify.log + true + + ${nacos.home}/logs/cfg-notify.log.%d{yyyy-MM-dd}.%i + 1GB + 15 + 3GB + true + + + %date %level %msg%n%n + GBK + + + + + ${nacos.home}/logs/cfg-app.log + true + + ${nacos.home}/logs/cfg-app.log.%d{yyyy-MM-dd}.%i + 20MB + 15 + 128MB + true + + + %date %level %msg%n%n + GBK + + + + + ${nacos.home}/logs/cfg-server.log + true + + ${nacos.home}/logs/cfg-server.log.%d{yyyy-MM-dd}.%i + 50MB + 15 + 512MB + true + + + %date %level %msg%n%n + GBK + + + + + ${nacos.home}/logs/nacos.log + true + + ${nacos.home}/logs/nacos.log.%d{yyyy-MM-dd}.%i + 50MB + 15 + 512MB + true + + + %date %level %msg%n%n + UTF-8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/distribution/conf/schema.sql b/distribution/conf/schema.sql new file mode 100644 index 00000000000..e5722e896a9 --- /dev/null +++ b/distribution/conf/schema.sql @@ -0,0 +1,161 @@ +CREATE SCHEMA nacos AUTHORIZATION nacos; + +CREATE TABLE config_info ( + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + app_name varchar(128), + content LONG VARCHAR NOT NULL, + md5 varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + src_user varchar(128) DEFAULT NULL, + src_ip varchar(20) DEFAULT NULL, + c_desc varchar(256) DEFAULT NULL, + c_use varchar(64) DEFAULT NULL, + effect varchar(64) DEFAULT NULL, + type varchar(64) DEFAULT NULL, + c_schema LONG VARCHAR DEFAULT NULL, + constraint configinfo_id_key PRIMARY KEY (id), + constraint uk_configinfo_datagrouptenant UNIQUE (data_id,group_id,tenant_id)); + +CREATE INDEX configinfo_dataid_key_idx ON config_info(data_id); +CREATE INDEX configinfo_groupid_key_idx ON config_info(group_id); +CREATE INDEX configinfo_dataid_group_key_idx ON config_info(data_id, group_id); + +CREATE TABLE his_config_info ( + id bigint NOT NULL, + nid bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + app_name varchar(128), + content LONG VARCHAR NOT NULL, + md5 varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00.000', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00.000', + src_user varchar(128), + src_ip varchar(20) DEFAULT NULL, + op_type char(10) DEFAULT NULL, + constraint hisconfiginfo_nid_key PRIMARY KEY (nid)); + +CREATE INDEX hisconfiginfo_dataid_key_idx ON his_config_info(data_id); +CREATE INDEX hisconfiginfo_gmt_create_idx ON his_config_info(gmt_create); +CREATE INDEX hisconfiginfo_gmt_modified_idx ON his_config_info(gmt_modified); + + +CREATE TABLE config_info_beta ( + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + app_name varchar(128), + content LONG VARCHAR NOT NULL, + beta_ips varchar(1024), + md5 varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + src_user varchar(128), + src_ip varchar(20) DEFAULT NULL, + constraint configinfobeta_id_key PRIMARY KEY (id), + constraint uk_configinfobeta_datagrouptenant UNIQUE (data_id,group_id,tenant_id)); + +CREATE TABLE config_info_tag ( + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + tag_id varchar(128) NOT NULL, + app_name varchar(128), + content LONG VARCHAR NOT NULL, + md5 varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + src_user varchar(128), + src_ip varchar(20) DEFAULT NULL, + constraint configinfotag_id_key PRIMARY KEY (id), + constraint uk_configinfotag_datagrouptenanttag UNIQUE (data_id,group_id,tenant_id,tag_id)); + +CREATE TABLE config_info_aggr ( + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + datum_id varchar(255) NOT NULL, + app_name varchar(128), + content LONG VARCHAR NOT NULL, + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + constraint configinfoaggr_id_key PRIMARY KEY (id), + constraint uk_configinfoaggr_datagrouptenantdatum UNIQUE (data_id,group_id,tenant_id,datum_id)); + +CREATE TABLE app_list ( + id bigint NOT NULL generated by default as identity, + app_name varchar(128) NOT NULL, + is_dynamic_collect_disabled smallint DEFAULT 0, + last_sub_info_collected_time timestamp DEFAULT '1970-01-01 08:00:00.0', + sub_info_lock_owner varchar(128), + sub_info_lock_time timestamp DEFAULT '1970-01-01 08:00:00.0', + constraint applist_id_key PRIMARY KEY (id), + constraint uk_appname UNIQUE (app_name)); + +CREATE TABLE app_configdata_relation_subs ( + id bigint NOT NULL generated by default as identity, + app_name varchar(128) NOT NULL, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', + constraint configdatarelationsubs_id_key PRIMARY KEY (id), + constraint uk_app_sub_config_datagroup UNIQUE (app_name, data_id, group_id)); + + +CREATE TABLE app_configdata_relation_pubs ( + id bigint NOT NULL generated by default as identity, + app_name varchar(128) NOT NULL, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', + constraint configdatarelationpubs_id_key PRIMARY KEY (id), + constraint uk_app_pub_config_datagroup UNIQUE (app_name, data_id, group_id)); + +CREATE TABLE config_tags_relation ( + id bigint NOT NULL, + tag_name varchar(128) NOT NULL, + tag_type varchar(64) DEFAULT NULL, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) DEFAULT '', + nid bigint NOT NULL generated by default as identity, + constraint config_tags_id_key PRIMARY KEY (nid), + constraint uk_configtagrelation_configidtag UNIQUE (id, tag_name, tag_type)); + +CREATE INDEX config_tags_tenant_id_idx ON config_tags_relation(tenant_id); + +CREATE TABLE group_capacity ( + id bigint NOT NULL generated by default as identity, + group_id varchar(128) DEFAULT '', + quota int DEFAULT 0, + usage int DEFAULT 0, + max_size int DEFAULT 0, + max_aggr_count int DEFAULT 0, + max_aggr_size int DEFAULT 0, + max_history_count int DEFAULT 0, + gmt_create timestamp DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', + constraint group_capacity_id_key PRIMARY KEY (id), + constraint uk_group_id UNIQUE (group_id)); + +CREATE TABLE tenant_capacity ( + id bigint NOT NULL generated by default as identity, + tenant_id varchar(128) DEFAULT '', + quota int DEFAULT 0, + usage int DEFAULT 0, + max_size int DEFAULT 0, + max_aggr_count int DEFAULT 0, + max_aggr_size int DEFAULT 0, + max_history_count int DEFAULT 0, + gmt_create timestamp DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', + constraint tenant_capacity_id_key PRIMARY KEY (id), + constraint uk_tenant_id UNIQUE (tenant_id)); + diff --git a/distribution/pom.xml b/distribution/pom.xml new file mode 100644 index 00000000000..acca208d3fd --- /dev/null +++ b/distribution/pom.xml @@ -0,0 +1,185 @@ + + + + com.alibaba.nacos + nacos-all + 0.1.0 + + + 4.0.0 + + nacos-distribution + nacos-distribution ${project.version} + pom + + + + com.alibaba.nacos + nacos-console + + + + + release-config + + + com.alibaba.nacos + nacos-config + + + + + + maven-assembly-plugin + + + release-config + + single + + package + + + release-config.xml + + false + + + + + + acm + + + + release-naming + + + com.alibaba.nacos + nacos-naming + + + + + + maven-assembly-plugin + + + release-naming + + single + + package + + + release-naming.xml + + false + + + + + + ans + + + + release-client + + + com.alibaba.nacos + nacos-client + + + + + + maven-assembly-plugin + + + release-client + + single + + package + + + release-client.xml + + false + + + + + + nacos-client + + + + release-core + + + com.alibaba.nacos + nacos-core + + + + + + maven-assembly-plugin + + + release-core + + single + + package + + + release-core.xml + + false + + + + + + nacos-core + + + + release-nacos + + + com.alibaba.nacos + nacos-console + + + + + + maven-assembly-plugin + 3.0.0 + + + release-nacos.xml + + + + + make-assembly + install + + single + + + + + + nacos + + + + \ No newline at end of file diff --git a/distribution/release-client.xml b/distribution/release-client.xml new file mode 100644 index 00000000000..153b014c451 --- /dev/null +++ b/distribution/release-client.xml @@ -0,0 +1,71 @@ + + + + client + false + + dir + tar.gz + zip + + + + ../ + + README.md + + + + + + conf/** + benchmark/* + + + + + + bin/* + + 0755 + + + + + + LICENSE-BIN + LICENSE + + + NOTICE-BIN + NOTICE + + + + + + true + + com.alibaba.nacos:nacos-client + + + lib/ + false + + + lib/ + + + + + + diff --git a/distribution/release-config.xml b/distribution/release-config.xml new file mode 100644 index 00000000000..5cf2505924b --- /dev/null +++ b/distribution/release-config.xml @@ -0,0 +1,71 @@ + + + + cfg + false + + dir + tar.gz + zip + + + + ../ + + README.md + + + + + + conf/** + benchmark/* + + + + + + bin/* + + 0755 + + + + + + LICENSE-BIN + LICENSE + + + NOTICE-BIN + NOTICE + + + + + + true + + com.alibaba.nacos:nacos-config + + + lib/ + false + + + lib/ + + + + + + diff --git a/distribution/release-core.xml b/distribution/release-core.xml new file mode 100644 index 00000000000..9c355288dee --- /dev/null +++ b/distribution/release-core.xml @@ -0,0 +1,70 @@ + + + + core + false + + dir + tar.gz + zip + + + + ../ + + README.md + + + + + + conf/** + + + + + + bin/* + + 0755 + + + + + + LICENSE-BIN + LICENSE + + + NOTICE-BIN + NOTICE + + + + + + true + + com.alibaba.nacos:nacos-core + + + lib/ + false + + + lib/ + + + + + + diff --git a/distribution/release-nacos.xml b/distribution/release-nacos.xml new file mode 100644 index 00000000000..c6248a84699 --- /dev/null +++ b/distribution/release-nacos.xml @@ -0,0 +1,50 @@ + + + + server-${project.version} + true + + dir + tar.gz + zip + + + + + + conf/** + + + + + + bin/* + + 0755 + + + + + LICENSE-BIN + LICENSE + + + NOTICE-BIN + NOTICE + + + + ../console/target/nacos-server.jar + /target/ + + + + + + true + + com.alibaba.nacos:nacos-console + + + + diff --git a/distribution/release-naming.xml b/distribution/release-naming.xml new file mode 100644 index 00000000000..426d98410e2 --- /dev/null +++ b/distribution/release-naming.xml @@ -0,0 +1,71 @@ + + + + naming + false + + dir + tar.gz + zip + + + + ../ + + README.md + + + + + + conf/** + benchmark/* + + + + + + bin/* + + 0755 + + + + + + LICENSE-BIN + LICENSE + + + NOTICE-BIN + NOTICE + + + + + + true + + com.alibaba.nacos:nacos-naming + + + lib/ + false + + + lib/ + + + + + + diff --git a/doc/Nacos_Logo.png b/doc/Nacos_Logo.png new file mode 100644 index 00000000000..7a4d5452f10 Binary files /dev/null and b/doc/Nacos_Logo.png differ diff --git a/doc/arch.png b/doc/arch.png new file mode 100644 index 00000000000..e69de29bb2d diff --git a/example/pom.xml b/example/pom.xml new file mode 100644 index 00000000000..fbb92de6985 --- /dev/null +++ b/example/pom.xml @@ -0,0 +1,42 @@ + + + + com.alibaba.nacos + nacos-all + 0.1.0 + + + 4.0.0 + + nacos-example + jar + + nacos-example ${project.version} + http://maven.apache.org + + + UTF-8 + + + + + junit + junit + test + + + ${project.groupId} + nacos-common + + + ${project.groupId} + nacos-core + + + com.alibaba.nacos + nacos-client + + + diff --git a/example/src/main/java/com/alibaba/nacos/example/App.java b/example/src/main/java/com/alibaba/nacos/example/App.java new file mode 100644 index 00000000000..020424a7d3f --- /dev/null +++ b/example/src/main/java/com/alibaba/nacos/example/App.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.example; + + +import java.util.Properties; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.naming.NamingFactory; +import com.alibaba.nacos.api.naming.NamingService; + +/** + * Hello world! + * + * @author xxc + */ +public class App { + public static void main(String[] args) throws NacosException { + Properties properties = new Properties(); + properties.setProperty("serverAddr", "21.34.53.5:8080,21.34.53.6:8080"); + properties.setProperty("namespace", "quickStart"); + NamingService naming = NamingFactory.createNamingService(properties); + naming.registerInstance("nacos.test.3", "11.11.11.11", 8888, "TEST1"); + naming.registerInstance("nacos.test.3", "2.2.2.2", 9999, "DEFAULT"); + System.out.println(naming.getAllInstances("nacos.test.3")); + } +} diff --git a/example/src/main/java/com/alibaba/nacos/example/ConfigExample.java b/example/src/main/java/com/alibaba/nacos/example/ConfigExample.java new file mode 100644 index 00000000000..4d0c7c20dd1 --- /dev/null +++ b/example/src/main/java/com/alibaba/nacos/example/ConfigExample.java @@ -0,0 +1,71 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.example; + +import java.util.Properties; +import java.util.concurrent.Executor; + +import com.alibaba.nacos.api.NacosFactory; +import com.alibaba.nacos.api.config.ConfigService; +import com.alibaba.nacos.api.config.listener.Listener; +import com.alibaba.nacos.api.exception.NacosException; + +/** + * Config service example + * + * @author Nacos + * + */ +public class ConfigExample { + + public static void main(String[] args) throws NacosException, InterruptedException { + String serverAddr = "localhost"; + String dataId = "testDataId"; + String group = "testGroup"; + Properties properties = new Properties(); + properties.put("serverAddr", serverAddr); + ConfigService configService = NacosFactory.createConfigService(properties); + String content = configService.getConfig(dataId, group, 5000); + System.out.println(content); + configService.addListener(dataId, group, new Listener() { + @Override + public void receiveConfigInfo(String configInfo) { + System.out.println("recieve:" + configInfo); + } + + @Override + public Executor getExecutor() { + return null; + } + }); + + boolean isPublishOk = configService.publishConfig(dataId, group, "content"); + System.out.println(isPublishOk); + + Thread.sleep(3000); + content = configService.getConfig(dataId, group, 5000); + System.out.println(content); + + boolean isRemoveOk = configService.removeConfig(dataId, group); + System.out.println(isRemoveOk); + Thread.sleep(3000); + + content = configService.getConfig(dataId, group, 5000); + System.out.println(content); + Thread.sleep(3000); + + } +} diff --git a/example/src/main/java/com/alibaba/nacos/example/NamingExample.java b/example/src/main/java/com/alibaba/nacos/example/NamingExample.java new file mode 100644 index 00000000000..ab81bf392d0 --- /dev/null +++ b/example/src/main/java/com/alibaba/nacos/example/NamingExample.java @@ -0,0 +1,58 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.example; + +import java.util.Properties; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.naming.NamingFactory; +import com.alibaba.nacos.api.naming.NamingService; +import com.alibaba.nacos.api.naming.listener.Event; +import com.alibaba.nacos.api.naming.listener.EventListener; +import com.alibaba.nacos.api.naming.listener.NamingEvent; + +/** + * @author dungu.zpf + */ +public class NamingExample { + + public static void main(String[] args) throws NacosException { + + Properties properties = new Properties(); + properties.setProperty("serverAddr", System.getProperty("serverAddr")); + properties.setProperty("namespace", System.getProperty("namespace")); + + NamingService naming = NamingFactory.createNamingService(properties); + + naming.registerInstance("nacos.test.3", "11.11.11.11", 8888, "TEST1"); + + naming.registerInstance("nacos.test.3", "2.2.2.2", 9999, "DEFAULT"); + + System.out.println(naming.getAllInstances("nacos.test.3")); + + naming.deregisterInstance("nacos.test.3", "2.2.2.2", 9999, "DEFAULT"); + + System.out.println(naming.getAllInstances("nacos.test.3")); + + naming.subscribe("nacos.test.3", new EventListener() { + @Override + public void onEvent(Event event) { + System.out.println(((NamingEvent) event).getServiceName()); + System.out.println(((NamingEvent) event).getInstances()); + } + }); + } +} diff --git a/example/src/test/java/com/alibaba/nacos/example/AppTest.java b/example/src/test/java/com/alibaba/nacos/example/AppTest.java new file mode 100644 index 00000000000..600ef65cbbb --- /dev/null +++ b/example/src/test/java/com/alibaba/nacos/example/AppTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.example; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Unit test for simple App. + */ +public class AppTest + extends TestCase +{ + /** + * Create the test case + * + * @param testName name of the test case + */ + public AppTest( String testName ) + { + super( testName ); + } + + /** + * @return the suite of tests being tested + */ + public static Test suite() + { + return new TestSuite( AppTest.class ); + } + + /** + * Rigourous Test :-) + */ + public void testApp() + { + assertTrue( true ); + } +} diff --git a/naming/default/failover/nacos.test.4 b/naming/default/failover/nacos.test.4 new file mode 100644 index 00000000000..e68e914f4c6 --- /dev/null +++ b/naming/default/failover/nacos.test.4 @@ -0,0 +1 @@ +{"allIPs":false,"cacheMillis":1000,"checksum":"","clusters":"","dom":"nacos.test.4","hosts":[],"lastRefTime":0,"valid":true} \ No newline at end of file diff --git a/naming/pom.xml b/naming/pom.xml new file mode 100644 index 00000000000..67d2d75ad67 --- /dev/null +++ b/naming/pom.xml @@ -0,0 +1,186 @@ + + + + com.alibaba.nacos + nacos-all + 0.1.0 + + + 4.0.0 + + nacos-naming + jar + + nacos-naming ${project.version} + http://maven.apache.org + + + UTF-8 + + + + + junit + junit + test + + + ${project.groupId} + nacos-core + + + + com.alibaba + fastjson + + + + org.apache.commons + commons-lang3 + + + + io.netty + netty-all + + + + com.ning + async-http-client + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + commons-collections + commons-collections + + + org.codehaus.jackson + jackson-core-asl + + + + org.slf4j + slf4j-api + + + + ch.qos.logback + logback-classic + + + ch.qos.logback + logback-core + + + + org.apache.mina + mina-core + + + + com.google.guava + guava + + + + org.javatuples + javatuples + + + + org.apache.httpcomponents + httpcore + + + + org.apache.httpcomponents + httpclient + + + + mysql + mysql-connector-java + + + + + + org.slf4j + log4j-over-slf4j + + + + org.slf4j + jcl-over-slf4j + + + + org.slf4j + jul-to-slf4j + + + + com.github.spotbugs + spotbugs-annotations + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + -Dnacos.standalone=true + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + com.alibaba.nacos.naming.NamingApp + + + + jar-with-dependencies + + + + + + + + + org.codehaus.mojo + findbugs-maven-plugin + 3.0.4 + + + + + diff --git a/naming/src/main/java/com/alibaba/nacos/naming/NamingApp.java b/naming/src/main/java/com/alibaba/nacos/naming/NamingApp.java new file mode 100644 index 00000000000..c6524460253 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/NamingApp.java @@ -0,0 +1,35 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * Hello world! + * @author xxc + */ +@SpringBootApplication +@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class}) +public class NamingApp { + + public static void main(String[] args) { + SpringApplication.run(NamingApp.class, args); + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/acl/AuthChecker.java b/naming/src/main/java/com/alibaba/nacos/naming/acl/AuthChecker.java new file mode 100644 index 00000000000..ef8623e7073 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/acl/AuthChecker.java @@ -0,0 +1,118 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.acl; + +import com.alibaba.nacos.naming.core.Domain; +import com.alibaba.nacos.naming.core.DomainsManager; +import com.alibaba.nacos.naming.misc.Switch; +import com.alibaba.nacos.naming.misc.UtilsAndCommons; +import com.alibaba.nacos.naming.web.BaseServlet; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.security.AccessControlException; +import java.util.Map; + +/** + * @author dungu.zpf + */ +@Component +public class AuthChecker { + + @Autowired + private DomainsManager domainsManager; + + static private String[] APP_WHITE_LIST = {}; + static private String[] TOKEN_WHITE_LIST = {"traffic-scheduling@midware"}; + + public void doRaftAuth(HttpServletRequest req) throws Exception { + String token = req.getParameter("token"); + if (StringUtils.equals(UtilsAndCommons.SUPER_TOKEN, token)) { + return; + } + + String agent = req.getHeader("Client-Version"); + if (StringUtils.startsWith(agent, UtilsAndCommons.NACOS_SERVER_HEADER)) { + return; + } + + throw new IllegalAccessException("illegal access,agent= " + agent + ", token=" + token); + } + + public void doAuth(Map params, HttpServletRequest req) throws Exception { + String dom = BaseServlet.optional(req, "name", ""); + if (StringUtils.isEmpty(dom)) { + dom = BaseServlet.optional(req, "dom", ""); + } + + if (StringUtils.isEmpty(dom)) { + dom = BaseServlet.optional(req, "tag", ""); + } + + Domain domObj; + if (req.getRequestURI().equals(UtilsAndCommons.NACOS_NAMING_CONTEXT + UtilsAndCommons.API_UPDATE_SWITCH) || + req.getRequestURI().equals(UtilsAndCommons.NACOS_NAMING_CONTEXT + UtilsAndCommons.API_SET_ALL_WEIGHTS)) { + // we consider switch is a kind of special domain + domObj = Switch.getDom(); + } else { + domObj = domainsManager.getDomain(dom); + } + + if (domObj == null) { + if (!req.getRequestURI().equals(UtilsAndCommons.NACOS_NAMING_CONTEXT + UtilsAndCommons.API_SET_ALL_WEIGHTS)) { + throw new IllegalStateException("auth failed, dom does not exist: " + dom); + } + } + + String token = req.getParameter("token"); + String auth = req.getParameter("auth"); + String userName = req.getParameter("userName"); + if (StringUtils.isEmpty(auth) && StringUtils.isEmpty(token)) { + throw new IllegalArgumentException("provide 'authInfo' or 'token' to access this dom"); + } + + // try valid token + if ((domObj != null && StringUtils.equals(domObj.getToken(), token))) { + return; + } + + if (ArrayUtils.contains(TOKEN_WHITE_LIST, token)) { + return; + } + + if (ArrayUtils.contains(APP_WHITE_LIST, userName)) { + return; + } + + // if token failed, try AuthInfo + AuthInfo authInfo = AuthInfo.fromString(auth, BaseServlet.getAcceptEncoding(req)); + if (authInfo == null) { + throw new IllegalAccessException("invalid token or malformed auth info"); + } + + if (!ArrayUtils.contains(APP_WHITE_LIST, authInfo.getAppKey())) { + throw new AccessControlException("un-registered SDK app"); + } + + if (!domObj.getOwners().contains(authInfo.getOperator()) + && !Switch.getMasters().contains(authInfo.getOperator())) { + throw new AccessControlException("dom already exists and you're not among the owners"); + } + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/acl/AuthInfo.java b/naming/src/main/java/com/alibaba/nacos/naming/acl/AuthInfo.java new file mode 100644 index 00000000000..81557724f06 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/acl/AuthInfo.java @@ -0,0 +1,80 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.acl; + +/** + * @author dungu.zpf + */ +public class AuthInfo { + private String operator; + + private String appKey; + + public AuthInfo() {} + + public AuthInfo(String operator, String appKey) { + this.operator = operator; + this.appKey = appKey; + } + + public static AuthInfo fromString(String auth, String encoding) { + try { + String[] byteStrs = auth.split(","); + byte[] bytes = new byte[byteStrs.length]; + for(int i = 0; i < byteStrs.length; i++) { + bytes[i] = (byte)(~(Short.parseShort(byteStrs[i]))); + } + + String contentStr = new String(bytes, encoding); + String[] params = contentStr.split(":"); + return new AuthInfo(params[0], params[1]); + } catch (Throwable e) { + return null; + } + } + + @Override + public String toString() { + try { + // very simple encryption is enough + byte[] authBytes = (operator + ":" + appKey).getBytes("UTF-8"); + StringBuilder authBuilder = new StringBuilder(); + for (byte authByte : authBytes) { + authBuilder.append((byte) (~((short) authByte))).append(","); + } + + return authBuilder.substring(0, authBuilder.length() - 1); + } catch (Exception e) { + return "Error while encrypt AuthInfo" + e; + } + } + + public void setOperator(String operator) { + this.operator = operator; + } + + public String getOperator() { + return operator; + } + + public String getAppKey() { + return appKey; + } + + public void setAppKey(String appKey) { + this.appKey = appKey; + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/boot/RunningConfig.java b/naming/src/main/java/com/alibaba/nacos/naming/boot/RunningConfig.java new file mode 100644 index 00000000000..2791cb75b3d --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/boot/RunningConfig.java @@ -0,0 +1,66 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.boot; + +import com.alibaba.nacos.naming.misc.Loggers; +import com.alibaba.nacos.naming.raft.RaftCore; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.web.context.WebServerInitializedEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletContext; + +/** + * @author dungu.zpf + */ + +@Component +public class RunningConfig implements ApplicationListener { + + private static int serverPort; + + private static String contextPath; + + @Autowired + private ServletContext servletContext; + + @SuppressFBWarnings("ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD") + @Override + public void onApplicationEvent(WebServerInitializedEvent event) { + + Loggers.SRV_LOG.info("SERVER-INIT", "got port:" + event.getWebServer().getPort()); + Loggers.SRV_LOG.info("SERVER-INIT", "got path:" + servletContext.getContextPath()); + + serverPort = event.getWebServer().getPort(); + contextPath = servletContext.getContextPath(); + + try { + RaftCore.init(); + } catch (Exception e) { + Loggers.RAFT.error("VIPSRV-RAFT", "failed to initialize raft sub system", e); + } + } + + public static int getServerPort() { + return serverPort; + } + + public static String getContextPath() { + return contextPath; + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/CatalogController.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/CatalogController.java new file mode 100644 index 00000000000..0a7c79a460f --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/CatalogController.java @@ -0,0 +1,22 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.controllers; + +/** + * @author dungu.zpf + */ +public class CatalogController { +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/ClusterController.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/ClusterController.java new file mode 100644 index 00000000000..70738df9c8c --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/ClusterController.java @@ -0,0 +1,22 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.controllers; + +/** + * @author dungu.zpf + */ +public class ClusterController { +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/CmdbController.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/CmdbController.java new file mode 100644 index 00000000000..0d1f2715434 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/CmdbController.java @@ -0,0 +1,22 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.controllers; + +/** + * @author dungu.zpf + */ +public class CmdbController { +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/HealthController.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/HealthController.java new file mode 100644 index 00000000000..563e0b14cfc --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/HealthController.java @@ -0,0 +1,22 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.controllers; + +/** + * @author dungu.zpf + */ +public class HealthController { +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/InstanceController.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/InstanceController.java new file mode 100644 index 00000000000..c1412d046f1 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/InstanceController.java @@ -0,0 +1,172 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.controllers; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.nacos.naming.exception.NacosException; +import com.alibaba.nacos.naming.core.IpAddress; +import com.alibaba.nacos.naming.core.VirtualClusterDomain; +import com.alibaba.nacos.naming.healthcheck.HealthCheckMode; +import com.alibaba.nacos.naming.misc.UtilsAndCommons; +import com.alibaba.nacos.naming.web.ApiCommands; +import com.alibaba.nacos.naming.web.BaseServlet; +import com.alibaba.nacos.naming.web.MockHttpRequest; +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author dungu.zpf + */ +@RestController +@RequestMapping(UtilsAndCommons.NACOS_NAMING_CONTEXT) +public class InstanceController extends ApiCommands { + + @RequestMapping(value = "/instance", method = RequestMethod.PUT) + public String register(HttpServletRequest request) throws Exception { + + Map params = new HashMap<>(request.getParameterMap()); + MockHttpRequest mockHttpRequest = MockHttpRequest.buildRequest(params); + + String serviceJson = BaseServlet.optional(request, "service", StringUtils.EMPTY); + String clusterJson = BaseServlet.optional(request, "cluster", StringUtils.EMPTY); + + // set service info: + if (StringUtils.isNotEmpty(serviceJson)) { + JSONObject service = JSON.parseObject(serviceJson); + mockHttpRequest.addParameter("dom", service.getString("name")); + mockHttpRequest.addParameter("app", service.getString("app")); + mockHttpRequest.addParameter("group", service.getString("group")); + mockHttpRequest.addParameter("protectThreshold", service.getString("protectThreshold")); + + String healthCheckMode = service.getString("healthCheckMode"); + + if (HealthCheckMode.server.name().equals(healthCheckMode)) { + mockHttpRequest.addParameter("enableHealthCheck", "true"); + } + + if (HealthCheckMode.client.name().equals(healthCheckMode)) { + mockHttpRequest.addParameter("enableClientBeat", "true"); + } + + if (HealthCheckMode.none.name().equals(healthCheckMode)) { + mockHttpRequest.addParameter("enableHealthCheck", "false"); + mockHttpRequest.addParameter("enableClientBeat", "false"); + } + + mockHttpRequest.addParameter("serviceMetadata", service.getString("metadata")); + } else { + mockHttpRequest.addParameter("dom", BaseServlet.required(request, "serviceName")); + } + + // set cluster info: + if (StringUtils.isNotEmpty(clusterJson)) { + JSONObject cluster = JSON.parseObject(clusterJson); + String clusterName = cluster.getString("name"); + if (StringUtils.isEmpty(clusterName)) { + clusterName = UtilsAndCommons.DEFAULT_CLUSTER_NAME; + } + mockHttpRequest.addParameter("clusterName", clusterName); + + JSONObject healthChecker = cluster.getJSONObject("healthChecker"); + if (healthChecker == null) { + mockHttpRequest.addParameter("cktype", "TCP"); + } else { + for (String key : healthChecker.keySet()) { + mockHttpRequest.addParameter(key, healthChecker.getString(key)); + } + mockHttpRequest.addParameter("cktype", healthChecker.getString("type")); + } + + mockHttpRequest.addParameter("cluster", StringUtils.EMPTY); + mockHttpRequest.addParameter("defIPPort", cluster.getString("defaultPort")); + mockHttpRequest.addParameter("defCkport", cluster.getString("defaultCheckPort")); + mockHttpRequest.addParameter("ipPort4Check", cluster.getString("userIPPort4Check")); + mockHttpRequest.addParameter("clusterMetadata", cluster.getString("metadata")); + + } + + return regService(mockHttpRequest); + } + + @RequestMapping(value = "/instance", method = RequestMethod.DELETE) + public String deregister(HttpServletRequest request) throws Exception { + return deRegService(request); + } + + @RequestMapping(value = "/instance", method = RequestMethod.POST) + public String update(HttpServletRequest request) throws Exception { + return addIP4Dom(request); + } + + @RequestMapping(value = "/instances", method = RequestMethod.GET) + public JSONObject queryList(HttpServletRequest request) throws Exception { + + Map params = new HashMap<>(request.getParameterMap()); + params.put("dom", params.get("serviceName")); + MockHttpRequest mockHttpRequest = MockHttpRequest.buildRequest(params); + + return srvIPXT(mockHttpRequest); + } + + @RequestMapping(value = "/instance", method = RequestMethod.GET) + public JSONObject queryDetail(HttpServletRequest request) throws Exception { + + String serviceName = BaseServlet.required(request, "serviceName"); + String cluster = BaseServlet.optional(request, "cluster", UtilsAndCommons.DEFAULT_CLUSTER_NAME); + String ip = BaseServlet.required(request, "ip"); + int port = Integer.parseInt(BaseServlet.required(request, "port")); + + VirtualClusterDomain domain = (VirtualClusterDomain) domainsManager.getDomain(serviceName); + if (domain == null) { + throw new NacosException(NacosException.NOT_FOUND, "no dom " + serviceName + " found!"); + } + + List clusters = new ArrayList<>(); + clusters.add(cluster); + + List ips = domain.allIPs(clusters); + if (ips == null || ips.isEmpty()) { + throw new IllegalStateException("no ips found for cluster " + cluster + " in dom " + serviceName); + } + + for (IpAddress ipAddress : ips) { + if (ipAddress.getIp().equals(ip) && ipAddress.getPort() == port) { + JSONObject result = new JSONObject(); + result.put("service", serviceName); + result.put("ip", ip); + result.put("port", port); + result.put("clusterName", cluster); + result.put("weight", ipAddress.getWeight()); + result.put("healthy", ipAddress.isValid()); + result.put("metadata", ipAddress.getMetadata()); + result.put("instanceId", ipAddress.generateInstanceId()); + return result; + } + } + + throw new IllegalStateException("no matched ip found!"); + + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/OperatorController.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/OperatorController.java new file mode 100644 index 00000000000..328af72796c --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/OperatorController.java @@ -0,0 +1,22 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.controllers; + +/** + * @author dungu.zpf + */ +public class OperatorController { +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/ServiceController.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/ServiceController.java new file mode 100644 index 00000000000..efeed5319e6 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/ServiceController.java @@ -0,0 +1,28 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.controllers; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author dungu.zpf + */ +@RestController +@RequestMapping("/service") +public class ServiceController { + +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/core/Cluster.java b/naming/src/main/java/com/alibaba/nacos/naming/core/Cluster.java new file mode 100644 index 00000000000..e911e015edd --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/core/Cluster.java @@ -0,0 +1,461 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.core; + +import com.alibaba.fastjson.annotation.JSONField; +import com.alibaba.nacos.naming.healthcheck.AbstractHealthCheckConfig; +import com.alibaba.nacos.naming.healthcheck.HealthCheckReactor; +import com.alibaba.nacos.naming.healthcheck.HealthCheckStatus; +import com.alibaba.nacos.naming.healthcheck.HealthCheckTask; +import com.alibaba.nacos.naming.misc.Loggers; +import com.alibaba.nacos.naming.misc.Switch; +import com.alibaba.nacos.naming.misc.UtilsAndCommons; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author dungu.zpf + */ +public class Cluster implements Cloneable { + + private static final String CLUSTER_NAME_SYNTAX = "[0-9a-zA-Z-]+"; + + private String name; + /** + * in fact this is CIDR(Classless Inter-Domain Routing). for naming it 'submask' it has historical reasons + */ + private String submask = "0.0.0.0/0"; + /** + * a addition for same site routing, can group multiple sites into a region, like Hangzhou, Shanghai, etc. + */ + private String sitegroup = StringUtils.EMPTY; + + private int defCkport = 80; + + private int defIPPort = -1; + + private boolean useIPPort4Check = true; + + @JSONField(name = "nodegroup") + private String legacySyncConfig; + + @JSONField(name = "healthChecker") + private AbstractHealthCheckConfig healthChecker = new AbstractHealthCheckConfig.Tcp(); + + @JSONField(serialize = false) + private HealthCheckTask checkTask; + + @JSONField(serialize = false) + private Set ips = new HashSet(); + + @JSONField(serialize = false) + private Set raftIPs = new HashSet(); + + @JSONField(serialize = false) + private Domain dom; + + private Map ipContains = new ConcurrentHashMap<>(); + + private Map metadata = new ConcurrentHashMap<>(); + + public Cluster() { + } + + public int getDefIPPort() { + // for compatibility with old entries + return defIPPort == -1 ? defCkport : defIPPort; + } + + public void setDefIPPort(int defIPPort) { + if (defIPPort == 0) { + throw new IllegalArgumentException("defIPPort can not be 0"); + } + this.defIPPort = defIPPort; + } + + public List allIPs() { + return new ArrayList(chooseIPs()); + } + + public List allIPs(String tenant) { + + List list = new ArrayList<>(); + for (IpAddress ipAddress : chooseIPs()) { + if (ipAddress.getTenant().equals(tenant)) { + list.add(ipAddress); + } + } + return list; + } + + public List allIPs(String tenant, String app) { + + List list = new ArrayList<>(); + for (IpAddress ipAddress : chooseIPs()) { + if (ipAddress.getTenant().equals(tenant) && ipAddress.getApp().equals(app)) { + list.add(ipAddress); + } + } + return list; + } + + public void init() { + checkTask = new HealthCheckTask(this); + HealthCheckReactor.scheduleCheck(checkTask); + } + + public void destroy() { + checkTask.setCancelled(true); + } + + public void addIP(IpAddress ip) { + chooseIPs().add(ip); + } + + public void removeIP(IpAddress ip) { + chooseIPs().remove(ip); + } + + public HealthCheckTask getHealthCheckTask() { + return checkTask; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Domain getDom() { + return dom; + } + + public void setDom(Domain dom) { + this.dom = dom; + } + + public String getLegacySyncConfig() { + return legacySyncConfig; + } + + public void setLegacySyncConfig(String nodegroup) { + this.legacySyncConfig = nodegroup; + } + + public AbstractHealthCheckConfig getHealthChecker() { + return healthChecker; + } + + public void setHealthChecker(AbstractHealthCheckConfig healthChecker) { + this.healthChecker = healthChecker; + } + + @Override + public Cluster clone() throws CloneNotSupportedException { + super.clone(); + Cluster cluster = new Cluster(); + + cluster.setHealthChecker(healthChecker.clone()); + cluster.setDom(getDom()); + cluster.ips = new HashSet(); + cluster.raftIPs = new HashSet(); + cluster.checkTask = null; + cluster.metadata = new HashMap<>(metadata); + return cluster; + } + + public void updateIPs(List ips, boolean diamond) { + HashMap oldIPMap = new HashMap<>(raftIPs.size()); + if (diamond) { + for (IpAddress ip : this.ips) { + oldIPMap.put(ip.getDatumKey(), ip); + } + } else { + for (IpAddress ip : this.raftIPs) { + oldIPMap.put(ip.getDatumKey(), ip); + } + } + + List updatedIPs = updatedIPs(ips, oldIPMap.values()); + if (updatedIPs.size() > 0) { + for (IpAddress ip : updatedIPs) { + IpAddress oldIP = oldIPMap.get(ip.getDatumKey()); + + if (responsible(ip)) { + // do not update the ip validation status of updated ips + // because the checker has the most precise result + + // Only when ip is not marked, don't we update the health status of IP: + if (!ip.isMarked()) { + ip.setValid(oldIP.isValid()); + } + + } else { + if (ip.isValid() != oldIP.isValid()) { + // ip validation status updated + Loggers.EVT_LOG.info("{" + getDom().getName() + "} {SYNC} " + + "{IP-" + (ip.isValid() ? "ENABLED" : "DISABLED") + "} " + ip.getIp() + + ":" + ip.getPort() + "@" + name); + } + } + + if (ip.getWeight() != oldIP.getWeight()) { + // ip validation status updated + Loggers.EVT_LOG.info("{" + getDom().getName() + "} {SYNC} " + + "{IP-UPDATED} " + oldIP.toString() + "->" + ip.toString()); + } + } + } + + List newIPs = subtract(ips, oldIPMap.values()); + if (newIPs.size() > 0) { + Loggers.EVT_LOG.info("{" + getDom().getName() + "} {SYNC} {IP-NEW} cluster: " + name + + ", new ips(" + newIPs.size() + "): " + newIPs.toString()); + + for (IpAddress ip : newIPs) { + HealthCheckStatus.reset(ip); + } + } + + List deadIPs = subtract(oldIPMap.values(), ips); + + if (deadIPs.size() > 0) { + Loggers.EVT_LOG.info("{" + getDom().getName() + "} {SYNC} {IP-DEAD} cluster: " + name + + ", dead ips(" + deadIPs.size() + "): " + deadIPs.toString()); + + for (IpAddress ip : deadIPs) { + HealthCheckStatus.remv(ip); + } + } + + if (diamond) { + this.ips = new HashSet(ips); + } else { + this.raftIPs = new HashSet(ips); + } + StringBuilder stringBuilder = new StringBuilder(); + for (IpAddress ipAddress : raftIPs) { + stringBuilder.append(ipAddress.toIPAddr()).append(ipAddress.isValid()); + } + + ipContains.clear(); + + for (IpAddress ipAddress : raftIPs) { + ipContains.put(ipAddress.toIPAddr(), true); + } + + } + + public List updatedIPs(Collection a, Collection b) { + + List intersects = (List) CollectionUtils.intersection(a, b); + Map stringIPAddressMap = new ConcurrentHashMap<>(intersects.size()); + + for (IpAddress ipAddress : intersects) { + stringIPAddressMap.put(ipAddress.getIp() + ":" + ipAddress.getPort(), ipAddress); + } + + Map intersectMap = new ConcurrentHashMap<>(a.size() + b.size()); + Map ipAddressMap = new ConcurrentHashMap<>(a.size()); + Map ipAddressMap1 = new ConcurrentHashMap<>(b.size()); + Map ipAddressMap2 = new ConcurrentHashMap<>(a.size()); + + for (IpAddress ipAddress : b) { + if (stringIPAddressMap.containsKey(ipAddress.getIp() + ":" + ipAddress.getPort())) { + intersectMap.put(ipAddress.toString(), 1); + } + ipAddressMap1.put(ipAddress.toString(), ipAddress); + } + + + for (IpAddress ipAddress : a) { + if (stringIPAddressMap.containsKey(ipAddress.getIp() + ":" + ipAddress.getPort())) { + + if (intersectMap.containsKey(ipAddress.toString())) { + intersectMap.put(ipAddress.toString(), 2); + } else { + intersectMap.put(ipAddress.toString(), 1); + } + } + + ipAddressMap2.put(ipAddress.toString(), ipAddress); + + } + + for (Map.Entry entry : intersectMap.entrySet()) { + String key = entry.getKey(); + Integer value = entry.getValue(); + + if (value == 1) { + if (ipAddressMap2.containsKey(key)) { + ipAddressMap.put(key, ipAddressMap2.get(key)); + } + } + } + + return new ArrayList<>(ipAddressMap.values()); + } + + public List subtract(Collection a, Collection b) { + Map mapa = new HashMap<>(b.size()); + for (IpAddress o : b) { + mapa.put(o.getIp() + ":" + o.getPort(), o); + } + + List result = new ArrayList(); + + for (IpAddress o : a) { + if (!mapa.containsKey(o.getIp() + ":" + o.getPort())) { + result.add(o); + } + } + + return result; + } + + public Set chooseIPs() { + return raftIPs; + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Cluster)) { + return false; + } + + return name.equals(((Cluster) obj).getName()); + } + + public int getDefCkport() { + return defCkport; + } + + public void setDefCkport(int defCkport) { + this.defCkport = defCkport; + } + + public boolean isUseIPPort4Check() { + return useIPPort4Check; + } + + public void setUseIPPort4Check(boolean useIPPort4Check) { + this.useIPPort4Check = useIPPort4Check; + } + + public void update(Cluster cluster) { + + if (!healthChecker.equals(cluster.getHealthChecker())) { + Loggers.SRV_LOG.info("[CLUSTER-UPDATE] " + cluster.getDom().getName() + ":" + cluster.getName() + ", healthChecker: " + healthChecker.toString() + " -> " + cluster.getHealthChecker().toString()); + healthChecker = cluster.getHealthChecker(); + } + + if (defCkport != cluster.getDefCkport()) { + Loggers.SRV_LOG.info("[CLUSTER-UPDATE] " + cluster.getDom().getName() + ":" + cluster.getName() + ", defCkport: " + defCkport + " -> " + cluster.getDefCkport()); + defCkport = cluster.getDefCkport(); + } + + if (defIPPort != cluster.getDefIPPort()) { + Loggers.SRV_LOG.info("[CLUSTER-UPDATE] " + cluster.getDom().getName() + ":" + cluster.getName() + ", defIPPort: " + defIPPort + " -> " + cluster.getDefIPPort()); + defIPPort = cluster.getDefIPPort(); + } + + if (!StringUtils.equals(submask, cluster.getSubmask())) { + Loggers.SRV_LOG.info("[CLUSTER-UPDATE] " + cluster.getDom().getName() + ":" + cluster.getName() + ", submask: " + submask + " -> " + cluster.getSubmask()); + submask = cluster.getSubmask(); + } + + if (!StringUtils.equals(sitegroup, cluster.getSitegroup())) { + Loggers.SRV_LOG.info("[CLUSTER-UPDATE] " + cluster.getDom().getName() + ":" + cluster.getName() + ", sitegroup: " + sitegroup + " -> " + cluster.getSitegroup()); + sitegroup = cluster.getSitegroup(); + } + + if (useIPPort4Check != cluster.isUseIPPort4Check()) { + Loggers.SRV_LOG.info("[CLUSTER-UPDATE] " + cluster.getDom().getName() + ":" + cluster.getName() + ", useIPPort4Check: " + useIPPort4Check + " -> " + cluster.isUseIPPort4Check()); + useIPPort4Check = cluster.isUseIPPort4Check(); + } + + metadata = cluster.getMetadata(); + } + + public String getSyncKey() { + return ""; + } + + public String getSubmask() { + return submask; + } + + public void setSubmask(String submask) { + this.submask = submask; + } + + public String getSitegroup() { + return sitegroup; + } + + public void setSitegroup(String sitegroup) { + this.sitegroup = sitegroup; + } + + public Map getMetadata() { + return metadata; + } + + public void setMetadata(Map metadata) { + this.metadata = metadata; + } + + public boolean responsible(IpAddress ip) { + return Switch.isHealthCheckEnabled(dom.getName()) + && !getHealthCheckTask().isCancelled() + && DistroMapper.responsible(getDom().getName()) + && ipContains.containsKey(ip.toIPAddr()); + } + + public void valid() { + if (!name.matches(CLUSTER_NAME_SYNTAX)) { + throw new IllegalArgumentException("cluster name can only have these characters: 0-9a-zA-Z-, current: " + name); + } + + String[] cidrGroups = submask.split("\\|"); + for (String cidrGroup : cidrGroups) { + String[] cidrs = cidrGroup.split(","); + + for (String cidr : cidrs) { + if (!cidr.matches(UtilsAndCommons.CIDR_REGEX)) { + throw new IllegalArgumentException("malformed submask: " + submask + " for cluster: " + name); + } + } + } + } + + public static void main(String[] args) { + String v1 = "nesttest"; + if (v1.matches(CLUSTER_NAME_SYNTAX)) { + System.out.print(""); + } + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/core/DistroMapper.java b/naming/src/main/java/com/alibaba/nacos/naming/core/DistroMapper.java new file mode 100644 index 00000000000..9e7c3ab81ab --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/core/DistroMapper.java @@ -0,0 +1,496 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.core; + +import com.alibaba.fastjson.JSON; +import com.alibaba.nacos.naming.boot.RunningConfig; +import com.alibaba.nacos.naming.misc.*; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.apache.commons.collections.CollectionUtils; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +/** + * @author dungu.zpf + */ +public class DistroMapper { + + public static final int STABLE_PERIOD = 60 * 1000; + + public static List getHealthyList() { + return healthyList; + } + + private static List healthyList = new ArrayList(); + + private static Map> distroConfig = new ConcurrentHashMap>(); + + private static Set liveSites = new HashSet(); + + private static String localhostIP; + + public static final String LOCALHOST_SITE = UtilsAndCommons.UNKNOWN_SITE; + + private static long HEALTH_TIME_OUT_MILLIS = TimeUnit.SECONDS.toMillis(30); + + private static long LAST_HEALTH_SERVER_MILLIS = 0L; + + private static boolean AUTO_DISABLED_HEALTH_CHECK = false; + + private static Synchronizer synchronizer = new ServerStatusSynchronizer(); + + static { + try { + localhostIP = InetAddress.getLocalHost().getHostAddress() + ":" + RunningConfig.getServerPort(); + } catch (UnknownHostException e) { + throw new IllegalStateException("Unable to resolve current host IP"); + } + + init(); + + UtilsAndCommons.SERVER_STATUS_EXECUTOR.schedule(new ServerStatusReporter(), + 60000, TimeUnit.MILLISECONDS); + } + + /** + * init server list + */ + private static void init() { + List servers = NamingProxy.getServers(); + + while (servers == null || servers.size() == 0) { + Loggers.SRV_LOG.warn("DISTRO-MAPPER", "Server list is empty, sleep 3 seconds and try again."); + try { + TimeUnit.SECONDS.sleep(3); + servers = NamingProxy.getServers(); + } catch (InterruptedException e) { + Loggers.SRV_LOG.warn("DISTRO-MAPPER", "Sleeping thread is interupted, try again."); + } + } + + StringBuilder sb = new StringBuilder(); + + for (String serverIP : servers) { + String serverSite; + String serverConfig; + + serverSite = UtilsAndCommons.UNKNOWN_SITE; + + serverConfig = serverSite + "#" + serverIP + "#" + System.currentTimeMillis() + "#" + 1 + "\r\n"; + sb.append(serverConfig); + + } + + onServerStatusUpdate(sb.toString(), false); + } + + private static void onServerStatusUpdate(String configInfo, boolean isFromDiamond) { + + String[] configs = configInfo.split("\r\n"); + if (configs.length == 0) { + return; + } + + distroConfig.clear(); + List newHealthyList = new ArrayList(); + + for (String config : configs) { + // site:ip:lastReportTime:weight + String[] params = config.split("#"); + if (params.length <= 3) { + Loggers.SRV_LOG.warn("received malformed distro map data: " + config); + continue; + } + + Server server = new Server(); + + server.site = params[0]; + server.ip = params[1]; + server.lastRefTime = Long.parseLong(params[2]); + + Date date = new Date(Long.parseLong(params[2])); + server.lastRefTimeStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date); + + server.weight = params.length == 4 ? Integer.parseInt(params[3]) : 1; + server.alive = System.currentTimeMillis() - server.lastRefTime < Switch.getdistroServerExpiredMillis(); + + List list = distroConfig.get(server.site); + if (list == null) { + list = new ArrayList(); + distroConfig.put(server.site, list); + } + + list.add(server); + } + + liveSites.addAll(distroConfig.keySet()); + + List servers = distroConfig.get(LOCALHOST_SITE); + if (CollectionUtils.isEmpty(servers)) { + return; + } + + List allSiteSrvs = new ArrayList(); + for (Server server : servers) { + server.adWeight = Switch.getAdWeight(server.ip) == null ? 0 : Switch.getAdWeight(server.ip); + + for (int i = 0; i < server.weight + server.adWeight; i++) { + allSiteSrvs.add(server.ip); + + if (server.alive) { + newHealthyList.add(server.ip); + } + } + } + + Collections.sort(newHealthyList); + float curRatio = (float) newHealthyList.size() / allSiteSrvs.size(); + + if (AUTO_DISABLED_HEALTH_CHECK + && curRatio > Switch.getDistroThreshold() + && System.currentTimeMillis() - LAST_HEALTH_SERVER_MILLIS > STABLE_PERIOD) { + Loggers.SRV_LOG.info("VIPSRV-DISTRO", "distro threshold restored and " + + "stable now, enable health check. current ratio: " + curRatio); + + Switch.setHeathCheckEnabled(true); + + // we must set this variable, otherwise it will conflict with user's action + AUTO_DISABLED_HEALTH_CHECK = false; + } + + if (!CollectionUtils.isEqualCollection(healthyList, newHealthyList)) { + // for every change disable healthy check for some while + if (Switch.isHealthCheckEnabled()) { + Loggers.SRV_LOG.info("VIPSRV-DISTRO", "healthy server list changed, " + + "disable health check for " + STABLE_PERIOD + "ms from now on, healthList: " + healthyList + ",newHealthyList " + newHealthyList); + + Switch.setHeathCheckEnabled(false); + AUTO_DISABLED_HEALTH_CHECK = true; + + LAST_HEALTH_SERVER_MILLIS = System.currentTimeMillis(); + } + + healthyList = newHealthyList; + } + } + + public static synchronized void onReceiveServerStatus(String configInfo) { + String[] configs = configInfo.split("\r\n"); + if (configs.length == 0) { + return; + } + + List newHealthyList = new ArrayList(); + List tmpServerList = new ArrayList(); + + for (String config : configs) { + tmpServerList.clear(); + // site:ip:lastReportTime:weight + String[] params = config.split("#"); + if (params.length <= 3) { + Loggers.SRV_LOG.warn("received malformed distro map data: " + config); + continue; + } + + Server server = new Server(); + + server.site = params[0]; + server.ip = params[1]; + server.lastRefTime = Long.parseLong(params[2]); + + if (!NamingProxy.getServers().contains(server.ip)) { + throw new IllegalArgumentException("ip: " + server.ip + " is not in serverlist"); + } + + Date date = new Date(Long.parseLong(params[2])); + server.lastRefTimeStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date); + + server.weight = params.length == 4 ? Integer.parseInt(params[3]) : 1; + server.alive = System.currentTimeMillis() - server.lastRefTime < Switch.getdistroServerExpiredMillis(); + List list = distroConfig.get(server.site); + if (list == null || list.size() <= 0) { + list = new ArrayList(); + list.add(server); + distroConfig.put(server.site, list); + } + + for (Server s : list) { + String serverId = s.ip + "_" + s.site; + String newServerId = server.ip + "_" + server.site; + + if (serverId.equals(newServerId)) { + if (s.alive != server.alive || s.weight != server.weight) { + Loggers.SRV_LOG.warn("server beat out of date, current: " + JSON.toJSONString(server) + + ", last: " + JSON.toJSONString(s)); + } + tmpServerList.add(server); + continue; + } + tmpServerList.add(s); + } + + if (!tmpServerList.contains(server)) { + tmpServerList.add(server); + } + + distroConfig.put(server.site, tmpServerList); + + } + liveSites.addAll(distroConfig.keySet()); + + List servers = distroConfig.get(LOCALHOST_SITE); + if (CollectionUtils.isEmpty(servers)) { + return; + } + + //local site servers + List allLocalSiteSrvs = new ArrayList(); + for (Server server : servers) { + server.adWeight = Switch.getAdWeight(server.ip) == null ? 0 : Switch.getAdWeight(server.ip); + + for (int i = 0; i < server.weight + server.adWeight; i++) { + allLocalSiteSrvs.add(server.ip); + + if (server.alive) { + newHealthyList.add(server.ip); + } + } + } + + Collections.sort(newHealthyList); + float curRatio = (float) newHealthyList.size() / allLocalSiteSrvs.size(); + + if (AUTO_DISABLED_HEALTH_CHECK + && curRatio > Switch.getDistroThreshold() + && System.currentTimeMillis() - LAST_HEALTH_SERVER_MILLIS > STABLE_PERIOD) { + Loggers.SRV_LOG.info("VIPSRV-DISTRO", "distro threshold restored and " + + "stable now, enable health check. current ratio: " + curRatio); + + Switch.setHeathCheckEnabled(true); + + // we must set this variable, otherwise it will conflict with user's action + AUTO_DISABLED_HEALTH_CHECK = false; + } + + if (!CollectionUtils.isEqualCollection(healthyList, newHealthyList)) { + // for every change disable healthy check for some while + if (Switch.isHealthCheckEnabled()) { + Loggers.SRV_LOG.info("VIPSRV-DISTRO", "healthy server list changed, " + + "disable health check for " + STABLE_PERIOD + "ms from now on"); + + Switch.setHeathCheckEnabled(false); + AUTO_DISABLED_HEALTH_CHECK = true; + + LAST_HEALTH_SERVER_MILLIS = System.currentTimeMillis(); + } + + healthyList = newHealthyList; + } + } + + public static boolean responsible(String dom) { + if (!Switch.isDistroEnabled()) { + return true; + } + + if (CollectionUtils.isEmpty(healthyList)) { + // means distro config is not ready yet + return false; + } + + int index = healthyList.indexOf(localhostIP); + int lastIndex = healthyList.lastIndexOf(localhostIP); + if (lastIndex < 0 || index < 0) { + return true; + } + + int target = distroHash(dom) % healthyList.size(); + return target >= index && target <= lastIndex; + } + + public static String mapSrv(String dom) { + if (CollectionUtils.isEmpty(healthyList) || !Switch.isDistroEnabled()) { + return localhostIP; + } + + try { + return healthyList.get(distroHash(dom) % healthyList.size()); + } catch (Exception e) { + Loggers.SRV_LOG.warn("distro mapper failed, return localhost: " + localhostIP, e); + + return localhostIP; + } + } + + public static int distroHash(String dom) { + return Math.abs(dom.hashCode() % Integer.MAX_VALUE); + } + + public static String mapSrvName(String dom) { + return UtilsAndCommons.UNKNOWN_HOST; + } + + public static Set getLiveSites() { + return liveSites; + } + + public static boolean liveSite(String site) { + return liveSites.contains(site); + } + + public static void clean() { + cleanInvalidServers(); + + for (Map.Entry> entry : distroConfig.entrySet()) { + for (Server server : entry.getValue()) { + //request other server to clean invalid servers + if (!server.ip.equals(localhostIP)) { + requestOtherServerCleanInvalidServers(server.ip); + } + } + + } + } + + public static void cleanWithoutDiamond() { + cleanInvalidServers(); + } + + private static void cleanInvalidServers() { + + for (Map.Entry> entry : distroConfig.entrySet()) { + List tmpServers = null; + List currentServerList = entry.getValue(); + + for (Server server : entry.getValue()) { + if (!server.alive) { + + tmpServers = new ArrayList(); + + for (Server server1 : currentServerList) { + String serverKey1 = server1.ip + "_" + server1.site; + String serverKey = server.ip + "_" + server.site; + + if (!serverKey.equals(serverKey1) && !tmpServers.contains(server1)) { + tmpServers.add(server1); + } + } + } + } + if (tmpServers != null) { + distroConfig.put(entry.getKey(), tmpServers); + } + } + } + + private static void requestOtherServerCleanInvalidServers(String serverIP) { + Map params = new HashMap(1); + + params.put("action", "without-diamond-clean"); + try { + NamingProxy.reqAPI("distroStatus", params, serverIP, false); + } catch (Exception e) { + Loggers.SRV_LOG.warn("DISTRO-STATUS-CLEAN", "Failed to request to clean server status to " + serverIP, e); + } + } + + public static String getLocalhostIP() { + return localhostIP; + } + + @SuppressFBWarnings("URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD") + public static class Server { + public String site = UtilsAndCommons.UNKNOWN_SITE; + public String ip; + public int weight = 1; + /** + * additional weight, used to adjust manually + */ + public int adWeight; + + public boolean alive = false; + + public long lastRefTime = 0L; + public String lastRefTimeStr; + + } + + private static class ServerStatusReporter implements Runnable { + + @Override + public void run() { + try { + for (String key : distroConfig.keySet()) { + for (Server server : distroConfig.get(key)) { + server.alive = System.currentTimeMillis() - server.lastRefTime < Switch.getdistroServerExpiredMillis(); + } + } + + int weight = Runtime.getRuntime().availableProcessors() / 2; + if (weight <= 0) { + weight = 1; + } + + localhostIP = InetAddress.getLocalHost().getHostAddress() + ":" + RunningConfig.getServerPort(); + + long curTime = System.currentTimeMillis(); + String status = LOCALHOST_SITE + "#" + localhostIP + "#" + curTime + "#" + weight + "\r\n"; + + //send status to itself + onReceiveServerStatus(status); + + List allServers = NamingProxy.getServers(); + + if (!allServers.contains(localhostIP)) { + return; + } + + if (allServers.size() > 0 && !localhostIP.contains(UtilsAndCommons.LOCAL_HOST_IP)) { + for (String server : allServers) { + if (server.equals(localhostIP)) { + continue; + } + + if (!allServers.contains(localhostIP)) { + Loggers.SRV_LOG.error("NA", "local ip is not in serverlist, ip: " + localhostIP + ", serverlist: " + allServers); + return; + } + + Message msg = new Message(); + msg.setData(status); + + synchronizer.send(server, msg); + + } + } + } catch (Exception e) { + Loggers.SRV_LOG.error("SERVER-STATUS", "Exception while sending server status: ", e); + } finally { + UtilsAndCommons.SERVER_STATUS_EXECUTOR.schedule(this, Switch.getServerStatusSynchronizationPeriodMillis(), TimeUnit.MILLISECONDS); + } + + } + } + + public static Map> getDistroConfig() { + return distroConfig; + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/core/Domain.java b/naming/src/main/java/com/alibaba/nacos/naming/core/Domain.java new file mode 100644 index 00000000000..17082e90ca0 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/core/Domain.java @@ -0,0 +1,132 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.core; + +import java.util.List; + +/** + * @author dungu.zpf + */ +public interface Domain { + /** + * Get name of domain + * + * @return Name of domain + */ + String getName(); + + /** + * Set name of domain + * + * @param name Domain name + */ + void setName(String name); + + /** + * Get token of domain + * + * @return Token of domain + */ + String getToken(); + + /** + * Set token of domain + * + * @param token Domain token + */ + void setToken(String token); + + /** + * Get domain owners + * + * @return Domain owners + */ + List getOwners(); + + /** + * Set domain owners + * + * @param owners Domain owners + */ + void setOwners(List owners); + + /** + * Initiation of domain + */ + void init(); + + /** + * Domain destruction + * + * @throws Exception + */ + void destroy() throws Exception; + + /** + * Get whole list IP of domain + * + * @return Whole list IP of domain + */ + List allIPs(); + + /** + * Get servable IP list of domain. + * + * @param clientIP Request IP of client + * @return Servable IP list of domain. + */ + List srvIPs(String clientIP); + + /** + * get JSON serialization of domain + * + * @return JSON representation of domain + */ + String toJSON(); + + /** + * Set protect threshold of domain + * + * @param protectThreshold Protect threshold + */ + void setProtectThreshold(float protectThreshold); + + /** + * Get protect threshold of domain + * + * @return Protect threshold of domain + */ + float getProtectThreshold(); + + /** + * Replace domain using properties of 'dom' + * + * @param dom New domain + */ + void update(Domain dom); + + /** + * Get checksum of domain + * + * @return Checksum of domain + */ + String getChecksum(); + + /** + * Refresh checksum of domain + */ + void recalculateChecksum(); +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/core/DomainsManager.java b/naming/src/main/java/com/alibaba/nacos/naming/core/DomainsManager.java new file mode 100644 index 00000000000..92b898e8e06 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/core/DomainsManager.java @@ -0,0 +1,718 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.core; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.TypeReference; +import com.alibaba.nacos.common.util.Pair; +import com.alibaba.nacos.naming.misc.*; +import com.alibaba.nacos.naming.monitor.PerformanceLoggerThread; +import com.alibaba.nacos.naming.push.PushService; +import com.alibaba.nacos.naming.raft.Datum; +import com.alibaba.nacos.naming.raft.RaftCore; +import com.alibaba.nacos.naming.raft.RaftListener; +import com.alibaba.nacos.naming.raft.RaftPeer; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * @author dungu.zpf + */ +@Component +public class DomainsManager { + private Map domMap = new ConcurrentHashMap<>(); + private Map raftDomMap = new ConcurrentHashMap<>(); + private static Map> appName2Doms = new ConcurrentHashMap<>(); + + private LinkedBlockingDeque toBeUpdatedDomsQueue = new LinkedBlockingDeque<>(1024 * 1024); + + private Synchronizer synchronizer = new DomainStatusSynchronizer(); + + /** + * thread pool core size + */ + private final static int DOMAIN_UPDATE_EXECUTOR_NUM = 2; + + private final Lock lock = new ReentrantLock(); + + private Map dom2LockMap = new ConcurrentHashMap<>(); + + /** + * thread pool that processes getting domain detail from other server asynchronously + */ + private ExecutorService domainUpdateExecutor + = Executors.newFixedThreadPool(DOMAIN_UPDATE_EXECUTOR_NUM, new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setName("com.alibaba.nacos.naming.domain.update.http.handler"); + t.setDaemon(true); + return t; + } + }); + + public Map chooseDomMap() { + return raftDomMap; + } + + private void initConfig() { + + RaftPeer leader; + while (true) { + + try { + TimeUnit.SECONDS.sleep(5); + } catch (InterruptedException e) { + Loggers.SRV_LOG.error("AUTO-INIT", "failed to auto init", e); + } + + try { + leader = RaftCore.getPeerSet().getLeader(); + if (leader != null) { + Loggers.SRV_LOG.info("AUTO-INIT", "no leader now, sleep 3 seconds and try again."); + break; + } + + } catch (Throwable throwable) { + Loggers.SRV_LOG.error("AUTO-INIT", "failed to auto init", throwable); + } + + } + } + + + public void addUpdatedDom2Queue(String domName, String serverIP, String checksum) { + lock.lock(); + try { + toBeUpdatedDomsQueue.offer(new DomainKey(domName, serverIP, checksum), 5, TimeUnit.MILLISECONDS); + } catch (Exception e) { + toBeUpdatedDomsQueue.poll(); + toBeUpdatedDomsQueue.add(new DomainKey(domName, serverIP, checksum)); + Loggers.SRV_LOG.error("DOMAIN-STATUS", "Failed to add domain to be updatd to queue.", e); + } finally { + lock.unlock(); + } + } + + private class UpdatedDomainProcessor implements Runnable { + //get changed domain from other server asynchronously + + @Override + public void run() { + String domName = null; + String serverIP = null; + String checksum; + + try { + while (true) { + DomainKey domainKey = null; + + try { + domainKey = toBeUpdatedDomsQueue.take(); + } catch (Exception e) { + Loggers.EVT_LOG.error("UPDATE-DOMAIN", "Exception while taking item from LinkedBlockingDeque."); + } + + if (domainKey == null) { + continue; + } + + domName = domainKey.getDomName(); + serverIP = domainKey.getServerIP(); + checksum = domainKey.getChecksum(); + + domainUpdateExecutor.execute(new DomUpdater(domName, serverIP)); + } + } catch (Exception e) { + Loggers.EVT_LOG.error("UPDATE-DOMAIN", "Exception while update dom: " + domName + "from " + serverIP, e); + } + } + } + + private class DomUpdater implements Runnable { + String domName; + String serverIP; + + public DomUpdater(String domName, String serverIP) { + this.domName = domName; + this.serverIP = serverIP; + } + + @Override + public void run() { + try { + updatedDom2(domName, serverIP); + } catch (Exception e) { + Loggers.SRV_LOG.warn("DOMAIN-UPDATER", "Exception while update dom: " + domName + "from " + serverIP, e); + } + } + } + + public void updatedDom2(String domName, String serverIP) { + Message msg = synchronizer.get(serverIP, domName); + JSONObject dom = JSON.parseObject(msg.getData()); + + JSONArray ipList = dom.getJSONArray("ips"); + Map ipsMap = new HashMap<>(ipList.size()); + for (int i=0; i ipAddresses = raftVirtualClusterDomain.allIPs(); + for (IpAddress ipAddress : ipAddresses) { + Pair pair = ipsMap.get(ipAddress.toIPAddr()); + if (pair == null) { + continue; + } + Boolean valid = Boolean.parseBoolean(pair.getValue0()); + if (valid != ipAddress.isValid()) { + ipAddress.setValid(Boolean.parseBoolean(pair.getValue0())); + ipAddress.setInvalidType(pair.getValue1()); + Loggers.EVT_LOG.info("{" + domName + "} {SYNC} " + + "{IP-" + (ipAddress.isValid() ? "ENABLED" : "DISABLED") + "} " + ipAddress.getIp() + + ":" + ipAddress.getPort() + "@" + ipAddress.getClusterName()); + } + } + + PushService.domChanged(raftVirtualClusterDomain.getName()); + StringBuilder stringBuilder = new StringBuilder(); + List allIps = raftVirtualClusterDomain.allIPs(); + for (IpAddress ipAddress : allIps) { + stringBuilder.append(ipAddress.toIPAddr()).append("_").append(ipAddress.isValid()).append(","); + } + + Loggers.EVT_LOG.info("IP-UPDATED", "dom: " + raftVirtualClusterDomain.getName() + ", ips: " + stringBuilder.toString()); + + } + + public Set getAllDomNames() { + return new HashSet(chooseDomMap().keySet()); + } + + public void setAllDomNames(List allDomNames) { + this.allDomNames = new HashSet<>(allDomNames); + } + + public Set getAllDomNamesCache() { + if (Switch.isAllDomNameCache()) { + if (CollectionUtils.isNotEmpty(allDomNames)) { + return allDomNames; + } else { + allDomNames = getAllDomNames(); + } + } else { + return getAllDomNames(); + } + + return allDomNames; + } + + private Set allDomNames; + + public List getResponsibleDoms() { + List result = new ArrayList<>(); + Map domainMap = chooseDomMap(); + + for (Map.Entry entry : domainMap.entrySet()) { + Domain domain = entry.getValue(); + if (DistroMapper.responsible(entry.getKey())) { + result.add(domain); + } + } + + return result; + } + + public int getResponsibleIPCount() { + List responsibleDoms = getResponsibleDoms(); + int count = 0; + for (Domain domain : responsibleDoms) { + count += domain.allIPs().size(); + } + + return count; + } + + public void easyRemoveDom(String domName) throws Exception { + + Domain dom = raftDomMap.get(domName); + if (dom != null) { + RaftCore.signalDelete(UtilsAndCommons.getDomStoreKey(dom)); + } + } + + public void easyAddOrReplaceDom(Domain newDom) throws Exception { + VirtualClusterDomain virtualClusterDomain = null; + if (newDom instanceof VirtualClusterDomain) { + virtualClusterDomain = (VirtualClusterDomain) newDom; + newDom = virtualClusterDomain; + } + RaftCore.signalPublish(UtilsAndCommons.getDomStoreKey(newDom), JSON.toJSONString(newDom)); + } + + public void easyReplaceIP4Dom(String domName, String clusterName, List ips) throws Exception { + Domain dom = chooseDomMap().get(domName); + if (dom == null) { + throw new IllegalArgumentException("dom doesn't exist: " + domName); + } + + Cluster cluster = ((VirtualClusterDomain) dom).getClusterMap().get(clusterName); + if (cluster == null) { + throw new IllegalArgumentException("cluster doesn't exist: " + clusterName); + } + + List deadIPs = cluster.allIPs(); + deadIPs.removeAll(ips); + + easyAddIP4Dom(dom.getName(), ips); + easyRemvIP4Dom(dom.getName(), deadIPs); + } + + public void easyAddIP4Dom(String domName, List ips) throws Exception { + easyAddIP4Dom(domName, ips, -1); + } + + public void easyAddIP4Dom(String domName, List ips, long timestamp) throws Exception { + easyAddIP4Dom(domName, ips, timestamp, -1); + } + + public void easyAddIP4Dom(String domName, List ips, long timestamp, long term) throws Exception { + + try { + VirtualClusterDomain dom = (VirtualClusterDomain) chooseDomMap().get(domName); + if (dom == null) { + throw new IllegalArgumentException("dom doesn't exist: " + domName); + } + + // set default port and site info if missing + for (IpAddress ip : ips) { + if (ip.getPort() == 0) { + ip.setPort(dom.getClusterMap().get(ip.getClusterName()).getDefIPPort()); + } + } + + + Datum datum1 = RaftCore.getDatum(UtilsAndCommons.getIPListStoreKey(dom)); + String oldJson = StringUtils.EMPTY; + + if (datum1 != null) { + oldJson = datum1.value; + } + + List ipAddresses; + List currentIPs = dom.allIPs(); + Map map = new ConcurrentHashMap(currentIPs.size()); + + for (IpAddress ipAddress : currentIPs) { + map.put(ipAddress.toIPAddr(), ipAddress); + } + + ipAddresses = setValid(oldJson, map); + + Map ipAddressMap = new HashMap(ipAddresses.size()); + + for (IpAddress ipAddress : ipAddresses) { + ipAddressMap.put(ipAddress.getDatumKey(), ipAddress); + } + + for (IpAddress ipAddress : ips) { + if (!dom.getClusterMap().containsKey(ipAddress.getClusterName())) { + Loggers.SRV_LOG.info("cluster: " + ipAddress.getClusterName() + " not found, ip: " + ipAddress.toJSON()); + continue; + } + + ipAddressMap.put(ipAddress.getDatumKey(), ipAddress); + } + + if (ipAddressMap.size() <= 0) { + throw new IllegalArgumentException("ip list can not be empty, dom: " + dom.getName() + ", ip list: " + + JSON.toJSONString(ipAddressMap.values())); + } + + if (timestamp == -1) { + RaftCore.signalPublish(UtilsAndCommons.getIPListStoreKey(dom), + JSON.toJSONString(ipAddressMap.values())); + } else { + String key = UtilsAndCommons.getIPListStoreKey(dom); + String value = JSON.toJSONString(ipAddressMap.values()); + + Datum datum = new Datum(); + datum.key = key; + datum.value = value; + datum.timestamp = timestamp; + + RaftPeer peer = new RaftPeer(); + peer.ip = RaftCore.getLeader().ip; + peer.term.set(term); + peer.voteFor = RaftCore.getLeader().voteFor; + peer.heartbeatDueMs = RaftCore.getLeader().heartbeatDueMs; + peer.leaderDueMs = RaftCore.getLeader().leaderDueMs; + peer.state = RaftCore.getLeader().state; + + JSONObject json = new JSONObject(); + json.put("datum", datum); + json.put("source", peer); + + RaftCore.onPublish(json); + } + } finally { +// lock.unlock(); + } + } + + private List setValid(String oldJson, Map map) { + List ipAddresses = new ArrayList<>(); + if (StringUtils.isNotEmpty(oldJson)) { + try { + ipAddresses = JSON.parseObject(oldJson, new TypeReference>() { + }); + for (IpAddress ipAddress : ipAddresses) { + IpAddress ipAddress1 = map.get(ipAddress.toIPAddr()); + if (ipAddress1 != null) { + ipAddress.setValid(ipAddress1.isValid()); + } + } + } catch (Throwable throwable) { + Loggers.RAFT.error("NA", "error while processing json: " + oldJson, throwable); + } finally { + if (ipAddresses == null) { + ipAddresses = new ArrayList<>(); + } + } + } + + return ipAddresses; + } + + public void easyRemvIP4Dom(String domName, List ips) throws Exception { + Lock lock = dom2LockMap.get(domName); + if (lock == null) { + throw new IllegalStateException("no lock for " + domName + ", operation is disabled now."); + } + + try { + lock.lock(); + Domain dom = chooseDomMap().get(domName); + if (dom == null) { + throw new IllegalArgumentException("domain doesn't exist: " + domName); + } + + Datum datum = RaftCore.getDatum(UtilsAndCommons.getIPListStoreKey(dom)); + String oldJson = StringUtils.EMPTY; + List currentIPs = dom.allIPs(); + + if (currentIPs.size() <= 0) { + return; + } + + Map map = new ConcurrentHashMap(currentIPs.size()); + + for (IpAddress ipAddress : currentIPs) { + map.put(ipAddress.toIPAddr(), ipAddress); + } + + if (datum != null) { + oldJson = datum.value; + } + + List ipAddrs = setValid(oldJson, map); + + ipAddrs.removeAll(ips); + + if (ipAddrs.size() <= 0 && dom.allIPs().size() > 1) { + throw new IllegalArgumentException("ip list can not be empty, dom: " + dom.getName() + ", ip list: " + + JSON.toJSONString(ipAddrs)); + } + + RaftCore.signalPublish(UtilsAndCommons.getIPListStoreKey(dom), JSON.toJSONString(ipAddrs)); + } finally { + lock.unlock(); + } + } + + public Domain getDomain(String domName) { + return chooseDomMap().get(domName); + } + + public List searchDomains(String regex) { + List result = new ArrayList(); + for (Map.Entry entry : chooseDomMap().entrySet()) { + Domain dom = entry.getValue(); + + String key = dom.getName() + ":" + ArrayUtils.toString(dom.getOwners()); + if (key.matches(regex)) { + result.add(dom); + } + } + + return result; + } + + public int getDomCount() { + return chooseDomMap().size(); + } + + public int getIPCount() { + int total = 0; + List doms = new ArrayList(getAllDomNames()); + for (String dom : doms) { + Domain domain = getDomain(dom); + total += (domain.allIPs().size()); + } + + return total; + } + + public Map getRaftDomMap() { + return raftDomMap; + } + + public List getPagedDom(int startPage, int pageSize) { + ArrayList domainList = new ArrayList(chooseDomMap().values()); + if (pageSize >= chooseDomMap().size()) { + return Collections.unmodifiableList(domainList); + } + + List resultList = new ArrayList(); + for (int i = 0; i < domainList.size(); i++) { + if (i < startPage * pageSize) { + continue; + } + + resultList.add(domainList.get(i)); + + if (resultList.size() >= pageSize) { + break; + } + } + + return resultList; + } + + public static class DomainChecksum { + public Map domName2Checksum = new HashMap(); + + public void addItem(String domName, String checksum) { + if (StringUtils.isEmpty(domName) || StringUtils.isEmpty(checksum)) { + Loggers.SRV_LOG.warn("DOMAIN-CHECKSUM", "domName or checksum is empty,domName: " + domName + " checksum: " + checksum); + return; + } + + domName2Checksum.put(domName, checksum); + } + } + + private class DomainReporter implements Runnable { + + @Override + public void run() { + try { + + DomainChecksum checksum = new DomainChecksum(); + + List allDomainNames = new ArrayList(getAllDomNames()); + + if (allDomainNames.size() <= 0) { + //ignore + return; + } + + for (String domName : allDomainNames) { + if (!DistroMapper.responsible(domName)) { + continue; + } + + Domain domain = getDomain(domName); + + if (domain == null || domain instanceof SwitchDomain) { + continue; + } + + domain.recalculateChecksum(); + + checksum.addItem(domName, domain.getChecksum()); + } + + Message msg = new Message(); + + msg.setData(JSON.toJSONString(checksum)); + + List sameSiteServers = NamingProxy.getSameSiteServers().get("sameSite"); + + if (sameSiteServers == null || sameSiteServers.size() <= 0 || !NamingProxy.getServers().contains(NetUtils.localIP())) { + return; + } + + for (String server : sameSiteServers) { + if (server.equals(NetUtils.localIP())) { + continue; + } + synchronizer.send(server, msg); + } + } catch (Exception e) { + Loggers.SRV_LOG.error("DOMAIN-STATUS", "Exception while sending domain status: ", e); + } finally { + UtilsAndCommons.DOMAIN_SYNCHRONIZATION_EXECUTOR.schedule(this, Switch.getDomStatusSynchronizationPeriodMillis(), TimeUnit.MILLISECONDS); + } + } + } + + public DomainsManager() { + // wait until distro-mapper ready because domain distribution check depends on it + while (DistroMapper.getLiveSites().size() == 0) { + try { + Thread.sleep(TimeUnit.SECONDS.toMillis(1L)); + } catch (InterruptedException ignore) { + } + } + + PerformanceLoggerThread performanceLoggerThread = new PerformanceLoggerThread(); + performanceLoggerThread.init(this); + + UtilsAndCommons.DOMAIN_SYNCHRONIZATION_EXECUTOR.schedule(new DomainReporter(), 60000, TimeUnit.MILLISECONDS); + + UtilsAndCommons.DOMAIN_UPDATE_EXECUTOR.submit(new UpdatedDomainProcessor()); + + UtilsAndCommons.INIT_CONFIG_EXECUTOR.submit(new Runnable() { + @Override + public void run() { + initConfig(); + } + }); + + final RaftListener raftListener = new RaftListener() { + @Override + public boolean interests(String key) { + return StringUtils.startsWith(key, UtilsAndCommons.DOMAINS_DATA_ID); + } + + @Override + public boolean matchUnlistenKey(String key) { + return StringUtils.equals(key, UtilsAndCommons.DOMAINS_DATA_ID + ".*"); + } + + @SuppressFBWarnings("JLM_JSR166_LOCK_MONITORENTER") + @Override + public void onChange(String key, String value) throws Exception { + try { + if (StringUtils.isEmpty(value)) { + Loggers.SRV_LOG.warn("received empty push from raft, key=" + key); + return; + } + + VirtualClusterDomain dom = VirtualClusterDomain.fromJSON(value); + if (dom == null) { + throw new IllegalStateException("dom parsing failed, json: " + value); + } + + Loggers.RAFT.info("RAFT-NOTIFIER", "datum is changed, key:" + key + ", value:" + value); + + Domain oldDom = raftDomMap.get(dom.getName()); + if (oldDom != null) { + oldDom.update(dom); + } else { + + if (!dom2LockMap.containsKey(dom.getName())) { + dom2LockMap.put(dom.getName(), new ReentrantLock()); + } + + Lock lock = dom2LockMap.get(dom.getName()); + + + synchronized (lock) { + raftDomMap.put(dom.getName(), dom); + dom.init(); + lock.notifyAll(); + } + + Loggers.SRV_LOG.info("[NEW-DOM-raft] " + dom.toJSON()); + } + + } catch (Throwable e) { + Loggers.SRV_LOG.error("VIPSRV-DOM", "error while processing dom update", e); + } + } + + @Override + public void onDelete(String key, String value) throws Exception { + String name = StringUtils.removeStart(key, UtilsAndCommons.DOMAINS_DATA_ID + "."); + Domain dom = raftDomMap.remove(name); + Loggers.RAFT.info("RAFT-NOTIFIER", "datum is deleted, key:" + key + ", value:" + value); + + if (dom != null) { + dom.destroy(); + Loggers.SRV_LOG.info("[DEAD-DOM] " + dom.toJSON()); + } + } + }; + RaftCore.listen(raftListener); + + } + + public Lock addLock(String domName) { + Lock lock = new ReentrantLock(); + dom2LockMap.put(domName, lock); + return lock; + } + + public Map getDomMap() { + return new HashMap(domMap); + } + + private static class DomainKey { + private String domName; + private String serverIP; + + public String getChecksum() { + return checksum; + } + + public String getServerIP() { + return serverIP; + } + + public String getDomName() { + return domName; + } + + private String checksum; + + public DomainKey(String domName, String serverIP, String checksum) { + this.domName = domName; + this.serverIP = serverIP; + this.checksum = checksum; + } + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/core/IpAddress.java b/naming/src/main/java/com/alibaba/nacos/naming/core/IpAddress.java new file mode 100644 index 00000000000..10e9d15eb0c --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/core/IpAddress.java @@ -0,0 +1,399 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.core; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.annotation.JSONField; +import com.alibaba.nacos.naming.healthcheck.HealthCheckStatus; +import com.alibaba.nacos.naming.misc.Loggers; +import com.alibaba.nacos.naming.misc.UtilsAndCommons; +import org.apache.commons.lang3.math.NumberUtils; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * IP under domain + * + * @author dungu.zpf + */ +public class IpAddress implements Comparable { + + private static final double MAX_WEIGHT_VALUE = 10000.0D; + private static final double MIN_POSTIVE_WEIGHT_VALUE = 0.01D; + private static final double MIN_WEIGHT_VALUE = 0.00D; + + private String ip; + private int port = 0; + private double weight = 1.0; + private String clusterName = UtilsAndCommons.DEFAULT_CLUSTER_NAME; + private volatile long lastBeat = System.currentTimeMillis(); + + @JSONField(serialize = false) + private String invalidType = InvalidType.VALID; + + public static class InvalidType { + public final static String HTTP_404 = "404"; + public final static String WEIGHT_0 = "weight_0"; + public final static String NORMAL_INVALID = "invalid"; + public final static String VALID = "valid"; + } + + public String getInvalidType() { + return invalidType; + } + + public void setInvalidType(String invalidType) { + this.invalidType = invalidType; + } + + @JSONField(serialize = false) + private Cluster cluster; + + private volatile boolean valid = true; + + @JSONField(serialize = false) + private volatile boolean mockValid = false; + + @JSONField(serialize = false) + private volatile boolean preValid = true; + + private volatile boolean marked = false; + + private String tenant; + + private String app; + + private Map metadata = new ConcurrentHashMap<>(); + + public static final Pattern IP_PATTERN + = Pattern.compile("(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}):?(\\d{1,5})?"); + + public static final String SPLITER = "_"; + + public IpAddress() { + } + + public boolean isMockValid() { + return mockValid; + } + + public void setMockValid(boolean mockValid) { + this.mockValid = mockValid; + } + + public long getLastBeat() { + return lastBeat; + } + + public void setLastBeat(long lastBeat) { + this.lastBeat = lastBeat; + } + + public IpAddress(String ip, int port) { + this.ip = ip.trim(); + this.port = port; + this.clusterName = UtilsAndCommons.DEFAULT_CLUSTER_NAME; + } + + public IpAddress(String ip, int port, String clusterName) { + this.ip = ip.trim(); + this.port = port; + this.clusterName = clusterName; + } + + public IpAddress(String ip, int port, String clusterName, String tenant, String app) { + this.ip = ip.trim(); + this.port = port; + this.clusterName = clusterName; + this.tenant = tenant; + this.app = app; + } + + public static IpAddress fromString(String config) { + String[] ipAddressAttributes = config.split(SPLITER); + if (ipAddressAttributes.length < 1) { + return null; + } + + String provider = ipAddressAttributes[0]; + Matcher matcher = IP_PATTERN.matcher(provider); + if (!matcher.matches()) { + return null; + } + + int expectedGroupCount = 2; + + int port = 0; + if (NumberUtils.isNumber(matcher.group(expectedGroupCount))) { + port = Integer.parseInt(matcher.group(expectedGroupCount)); + } + + IpAddress ipAddress = new IpAddress(matcher.group(1), port); + + // 7 possible formats of config: + // ip:port + // ip:port_weight + // ip:port_weight_cluster + // ip:port_weight_valid + // ip:port_weight_valid_cluster + // ip:port_weight_valid_marked + // ip:port_weight_valid_marked_cluster + int minimumLength = 1; + + if (ipAddressAttributes.length > minimumLength) { + // determine 'weight': + ipAddress.setWeight(NumberUtils.toDouble(ipAddressAttributes[minimumLength], 1)); + } + + minimumLength++; + + if (ipAddressAttributes.length > minimumLength) { + // determine 'valid': + if (Boolean.TRUE.toString().equals(ipAddressAttributes[minimumLength]) || + Boolean.FALSE.toString().equals(ipAddressAttributes[minimumLength])) { + ipAddress.setValid(Boolean.parseBoolean(ipAddressAttributes[minimumLength])); + } + + // determine 'cluster': + if (!Boolean.TRUE.toString().equals(ipAddressAttributes[ipAddressAttributes.length - 1]) && + !Boolean.FALSE.toString().equals(ipAddressAttributes[ipAddressAttributes.length - 1])) { + ipAddress.setClusterName(ipAddressAttributes[ipAddressAttributes.length - 1]); + } + } + + minimumLength++; + + if (ipAddressAttributes.length > minimumLength) { + // determine 'marked': + if (Boolean.TRUE.toString().equals(ipAddressAttributes[minimumLength]) || + Boolean.FALSE.toString().equals(ipAddressAttributes[minimumLength])) { + ipAddress.setMarked(Boolean.parseBoolean(ipAddressAttributes[minimumLength])); + } + } + + return ipAddress; + } + + public String toIPAddr() { + return ip + ":" + port; + } + + @Override + public String toString() { + return getDatumKey() + SPLITER + weight + SPLITER + valid + SPLITER + marked + SPLITER + clusterName; + } + + public String toJSON() { + return JSON.toJSONString(this); + } + + + public static IpAddress fromJSON(String json) { + IpAddress ip; + + try { + ip = JSON.parseObject(json, IpAddress.class); + } catch (Exception e) { + ip = fromString(json); + } + + if (ip == null) { + throw new IllegalArgumentException("malfomed ip config: " + json); + } + + if (ip.getWeight() > MAX_WEIGHT_VALUE) { + ip.setWeight(MAX_WEIGHT_VALUE); + } + + if (ip.getWeight() < MIN_POSTIVE_WEIGHT_VALUE && ip.getWeight() > MIN_WEIGHT_VALUE) { + ip.setWeight(MIN_POSTIVE_WEIGHT_VALUE); + } else if (ip.getWeight() < MIN_WEIGHT_VALUE) { + ip.setWeight(0.0D); + } + return ip; + } + + @Override + public boolean equals(Object obj) { + if (null == obj || obj.getClass() != getClass()) { + return false; + } + if (obj == this) { + return true; + } + IpAddress other = (IpAddress) obj; + + // 0 means wild + return ip.equals(other.getIp()) && (port == other.port || port == 0); + } + + @JSONField(serialize = false) + public String getDatumKey() { + if (port > 0) { + return ip + ":" + port + ":" + DistroMapper.LOCALHOST_SITE; + } else { + return ip + ":" + DistroMapper.LOCALHOST_SITE; + } + } + + @JSONField(serialize = false) + public String getDefaultKey() { + if (port > 0) { + return ip + ":" + port + ":" + UtilsAndCommons.UNKNOWN_SITE; + } else { + return ip + ":" + UtilsAndCommons.UNKNOWN_SITE; + } + } + + @Override + public int hashCode() { + return ip.hashCode(); + } + + public void setBeingChecked(boolean isBeingChecked) { + HealthCheckStatus.get(this).isBeingChecked.set(isBeingChecked); + } + + public boolean markChecking() { + return HealthCheckStatus.get(this).isBeingChecked.compareAndSet(false, true); + } + + @JSONField(serialize = false) + public long getCheckRT() { + return HealthCheckStatus.get(this).checkRT; + } + + @JSONField(serialize = false) + public AtomicInteger getOKCount() { + return HealthCheckStatus.get(this).checkOKCount; + } + + @JSONField(serialize = false) + public AtomicInteger getFailCount() { + return HealthCheckStatus.get(this).checkFailCount; + } + + @JSONField(serialize = false) + public void setCheckRT(long checkRT) { + HealthCheckStatus.get(this).checkRT = checkRT; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public String getClusterName() { + return clusterName; + } + + public void setClusterName(String clusterName) { + this.clusterName = clusterName; + } + + public void setCluster(Cluster cluster) { + this.cluster = cluster; + } + + public Cluster getCluster() { + return cluster; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public String getIp() { + return ip; + } + + public double getWeight() { + return weight; + } + + public void setWeight(double weight) { + this.weight = weight; + } + + public synchronized void setValid(boolean valid) { + this.preValid = this.valid; + this.valid = valid; + } + + public boolean isValid() { + return valid; + } + + public boolean isPreValid() { + return preValid; + } + + public boolean isMarked() { + return marked; + } + + public void setMarked(boolean marked) { + this.marked = marked; + } + + public String getApp() { + return app; + } + + public void setApp(String app) { + this.app = app; + } + + public String getTenant() { + return tenant; + } + + public void setTenant(String tenant) { + this.tenant = tenant; + } + + public Map getMetadata() { + return metadata; + } + + public void setMetadata(Map metadata) { + this.metadata = metadata; + } + + public String generateInstanceId() { + return this.ip + "-" + this.port + "-" + this.cluster.getName() + "-" + this.cluster.getDom().getName(); + } + + @Override + public int compareTo(Object o) { + if (!(o instanceof IpAddress)) { + Loggers.SRV_LOG.error("IPADDRESS-COMPARE", "Object is not an instance of IPAdress,object: " + o.getClass()); + throw new IllegalArgumentException("Object is not an instance of IPAdress,object: " + o.getClass()); + } + + IpAddress ipAddress = (IpAddress) o; + String ipKey = ipAddress.toString(); + + return this.toString().compareTo(ipKey); + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/core/VirtualClusterDomain.java b/naming/src/main/java/com/alibaba/nacos/naming/core/VirtualClusterDomain.java new file mode 100644 index 00000000000..320c7e8801f --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/core/VirtualClusterDomain.java @@ -0,0 +1,618 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.core; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.TypeReference; +import com.alibaba.fastjson.annotation.JSONField; +import com.alibaba.nacos.naming.healthcheck.ClientBeatCheckTask; +import com.alibaba.nacos.naming.healthcheck.ClientBeatProcessor; +import com.alibaba.nacos.naming.healthcheck.HealthCheckReactor; +import com.alibaba.nacos.naming.healthcheck.RsInfo; +import com.alibaba.nacos.naming.misc.Loggers; +import com.alibaba.nacos.naming.misc.NetUtils; +import com.alibaba.nacos.naming.misc.UtilsAndCommons; +import com.alibaba.nacos.naming.push.PushService; +import com.alibaba.nacos.naming.raft.RaftCore; +import com.alibaba.nacos.naming.raft.RaftListener; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.ListUtils; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.StringUtils; + +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.security.MessageDigest; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author dungu.zpf + */ +public class VirtualClusterDomain implements Domain, RaftListener { + + private static final String DOMAIN_NAME_SYNTAX = "[0-9a-zA-Z\\.:_-]+"; + + private String name; + private String token; + private List owners = new ArrayList<>(); + private Boolean resetWeight = false; + private Boolean enableHealthCheck = true; + private Boolean enabled = true; + private Boolean enableClientBeat = false; + + public static final int MINIMUM_IP_DELETE_TIMEOUT = 60 * 1000; + /** + * IP will be deleted if it has not send beat for some time, default timeout is half an hour . + */ + private long ipDeleteTimeout = 1800 * 1000; + + @JSONField(serialize = false) + private ClientBeatProcessor clientBeatProcessor = new ClientBeatProcessor(); + + @JSONField(serialize = false) + private ClientBeatCheckTask clientBeatCheckTask = new ClientBeatCheckTask(this); + + private volatile long lastModifiedMillis = 0L; + + private boolean useSpecifiedURL = false; + + private float protectThreshold = 0.0F; + + private volatile String checksum; + + private Map clusterMap = new HashMap(); + + private Map metadata = new ConcurrentHashMap<>(); + + public long getIpDeleteTimeout() { + return ipDeleteTimeout; + } + + public void setIpDeleteTimeout(long ipDeleteTimeout) { + this.ipDeleteTimeout = ipDeleteTimeout; + } + + public void processClientBeat(final RsInfo rsInfo) { + clientBeatProcessor.setDomain(this); + clientBeatProcessor.setRsInfo(rsInfo); + HealthCheckReactor.scheduleNow(clientBeatProcessor); + } + + public Boolean getEnableClientBeat() { + return enableClientBeat; + } + + public void setEnableClientBeat(Boolean enableClientBeat) { + this.enableClientBeat = enableClientBeat; + } + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public Boolean getEnableHealthCheck() { + return enableHealthCheck; + } + + public void setEnableHealthCheck(Boolean enableHealthCheck) { + this.enableHealthCheck = enableHealthCheck; + } + + public long getLastModifiedMillis() { + return lastModifiedMillis; + } + + public void setLastModifiedMillis(long lastModifiedMillis) { + this.lastModifiedMillis = lastModifiedMillis; + } + + public Boolean getResetWeight() { + return resetWeight; + } + + public void setResetWeight(Boolean resetWeight) { + this.resetWeight = resetWeight; + } + + public Map getMetadata() { + return metadata; + } + + public void setMetadata(Map metadata) { + this.metadata = metadata; + } + + public VirtualClusterDomain() { + + } + + @Override + public boolean interests(String key) { + return StringUtils.equals(key, UtilsAndCommons.IPADDRESS_DATA_ID_PRE + name); + } + + @Override + public boolean matchUnlistenKey(String key) { + return StringUtils.equals(key, UtilsAndCommons.IPADDRESS_DATA_ID_PRE + name); + } + + @Override + public void onChange(String key, String value) throws Exception { + + if (StringUtils.isEmpty(value)) { + Loggers.SRV_LOG.warn("VIPSRV-DOM", "received empty iplist config for dom: " + name); + } + + Loggers.RAFT.info("VIPSRV-RAFT", "datum is changed, key: " + key + ", value: " + value); + + List ips = JSON.parseObject(value, new TypeReference>() { + }); + for (IpAddress ip : ips) { + if (ip.getWeight() > 10000.0D) { + ip.setWeight(10000.0D); + } + + if (ip.getWeight() < 0.01D && ip.getWeight() > 0.0D) { + ip.setWeight(0.01D); + } + } + + updateIPs(ips, false); + + recalculateChecksum(); + } + + @Override + public void onDelete(String key, String value) throws Exception { + // ignore + } + + public void updateIPs(List ips, boolean diamond) { + if (CollectionUtils.isEmpty(ips) && allIPs().size() > 1) { + return; + } + + + Map> ipMap = new HashMap>(clusterMap.size()); + for (String clusterName : clusterMap.keySet()) { + ipMap.put(clusterName, new ArrayList()); + } + + for (IpAddress ip : ips) { + try { + if (ip == null) { + Loggers.SRV_LOG.error("VIPSRV-DOM", "received malformed ip"); + continue; + } + + if (ip.getPort() == 0) { + ip.setPort(getLegacyCkPort()); + } + + if (StringUtils.isEmpty(ip.getClusterName())) { + ip.setClusterName(UtilsAndCommons.DEFAULT_CLUSTER_NAME); + } + + // put wild ip into DEFAULT cluster + if (!clusterMap.containsKey(ip.getClusterName())) { + Loggers.SRV_LOG.warn("cluster of IP not found: " + ip.toJSON()); + continue; + } + + List clusterIPs = ipMap.get(ip.getClusterName()); + if (clusterIPs == null) { + clusterIPs = new LinkedList(); + ipMap.put(ip.getClusterName(), clusterIPs); + } + + clusterIPs.add(ip); + } catch (Exception e) { + Loggers.SRV_LOG.error("VIPSRV-DOM", "failed to process ip: " + ip, e); + } + } + + for (Map.Entry> entry : ipMap.entrySet()) { + //make every ip mine + List entryIPs = entry.getValue(); + for (IpAddress ip : entryIPs) { + ip.setCluster(clusterMap.get(ip.getClusterName())); + } + + clusterMap.get(entry.getKey()).updateIPs(entryIPs, diamond); + } + setLastModifiedMillis(System.currentTimeMillis()); + PushService.domChanged(name); + StringBuilder stringBuilder = new StringBuilder(); + + for (IpAddress ipAddress : allIPs()) { + stringBuilder.append(ipAddress.toIPAddr()).append("_").append(ipAddress.isValid()).append(","); + } + + Loggers.EVT_LOG.info("IP-UPDATED", "dom: " + getName() + ", ips: " + stringBuilder.toString()); + + } + + @Override + public void init() { + + RaftCore.listen(this); + HealthCheckReactor.scheduleCheck(clientBeatCheckTask); + + for (Map.Entry entry : clusterMap.entrySet()) { + entry.getValue().init(); + } + } + + @Override + public void destroy() throws Exception { + for (Map.Entry entry : clusterMap.entrySet()) { + entry.getValue().destroy(); + } + + if (RaftCore.isLeader(NetUtils.localIP())) { + RaftCore.signalDelete(UtilsAndCommons.getIPListStoreKey(this)); + } + + RaftCore.unlisten(UtilsAndCommons.getIPListStoreKey(this)); + } + + @Override + public List allIPs() { + List allIPs = new ArrayList(); + for (Map.Entry entry : clusterMap.entrySet()) { + allIPs.addAll(entry.getValue().allIPs()); + } + + return allIPs; + } + + public List allIPs(String tenant, String app) { + + List allIPs = new ArrayList(); + for (Map.Entry entry : clusterMap.entrySet()) { + + if (StringUtils.isEmpty(app)) { + allIPs.addAll(entry.getValue().allIPs(tenant)); + } else { + allIPs.addAll(entry.getValue().allIPs(tenant, app)); + } + } + + return allIPs; + } + + public List allIPs(List clusters) { + List allIPs = new ArrayList(); + for (String cluster : clusters) { + Cluster clusterObj = clusterMap.get(cluster); + if (clusterObj == null) { + throw new IllegalArgumentException("can not find cluster: " + cluster); + } + + allIPs.addAll(clusterObj.allIPs()); + } + + return allIPs; + } + + @Override + public List srvIPs(String clientIP) { + return srvIPs(clientIP, Collections.EMPTY_LIST); + } + + public List srvIPs(String clientIP, List clusters) { + List ips; + + if (CollectionUtils.isEmpty(clusters)) { + clusters = new ArrayList<>(); + clusters.addAll(clusterMap.keySet()); + } + return allIPs(clusters); + } + + public static VirtualClusterDomain fromJSON(String json) { + try { + VirtualClusterDomain vDom = JSON.parseObject(json, VirtualClusterDomain.class); + for (Cluster cluster : vDom.clusterMap.values()) { + cluster.setDom(vDom); + } + + return vDom; + } catch (Exception e) { + Loggers.SRV_LOG.error("VIPSRV-DOM", "parse cluster json error, " + e.toString() + ", content=" + json, e); + return null; + } + } + + @Override + public String toJSON() { + return JSON.toJSONString(this); + } + + @JSONField(serialize = false) + public String getDomString() { + Map domain = new HashMap(10); + VirtualClusterDomain vDom = this; + + domain.put("name", vDom.getName()); + + List ips = vDom.allIPs(); + int invalidIPCount = 0; + int ipCount = 0; + for (IpAddress ip : ips) { + if (!ip.isValid()) { + invalidIPCount++; + } + + ipCount++; + } + + domain.put("ipCount", ipCount); + domain.put("invalidIPCount", invalidIPCount); + + domain.put("owners", vDom.getOwners()); + domain.put("token", vDom.getToken()); + + domain.put("protectThreshold", vDom.getProtectThreshold()); + + int totalCkRTMillis = 0; + int validCkRTCount = 0; + + List clustersList = new ArrayList(); + + for (Map.Entry entry : vDom.getClusterMap().entrySet()) { + Cluster cluster = entry.getValue(); + + Map clusters = new HashMap(10); + clusters.put("name", cluster.getName()); + clusters.put("healthChecker", cluster.getHealthChecker()); + clusters.put("defCkport", cluster.getDefCkport()); + clusters.put("defIPPort", cluster.getDefIPPort()); + clusters.put("useIPPort4Check", cluster.isUseIPPort4Check()); + clusters.put("submask", cluster.getSubmask()); + clusters.put("sitegroup", cluster.getSitegroup()); + + clustersList.add(clusters); + } + + domain.put("clusters", clustersList); + + return JSON.toJSONString(domain); + } + + + /** + * the legacy check port is the default check port for old domain format + */ + @JSONField(serialize = false) + public int getLegacyCkPort() { + return clusterMap.get(UtilsAndCommons.DEFAULT_CLUSTER_NAME).getDefCkport(); + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + if (!name.matches(DOMAIN_NAME_SYNTAX)) { + throw new IllegalArgumentException("dom name can only have these characters: 0-9a-zA-Z.:_-; current: " + name); + } + + this.name = name; + } + + public boolean isUseSpecifiedURL() { + return useSpecifiedURL; + } + + public void setUseSpecifiedURL(boolean isUseSpecifiedURL) { + this.useSpecifiedURL = isUseSpecifiedURL; + } + + @Override + public String getToken() { + return token; + } + + @Override + public void setToken(String token) { + this.token = token; + } + + @Override + public List getOwners() { + return owners; + } + + @Override + public void setOwners(List owners) { + this.owners = owners; + } + + public Map getClusterMap() { + return clusterMap; + } + + public void setClusterMap(Map clusterMap) { + this.clusterMap = clusterMap; + } + + @Override + public void update(Domain dom) { + if (!(dom instanceof VirtualClusterDomain)) { + return; + } + + VirtualClusterDomain vDom = (VirtualClusterDomain) dom; + if (!StringUtils.equals(token, vDom.getToken())) { + Loggers.SRV_LOG.info("[DOM-UPDATE] dom: " + name + ",token" + token + " -> " + vDom.getToken()); + token = vDom.getToken(); + } + + if (!ListUtils.isEqualList(owners, vDom.getOwners())) { + Loggers.SRV_LOG.info("[DOM-UPDATE] dom: " + name + ",owners: " + owners + " -> " + vDom.getToken()); + owners = vDom.getOwners(); + } + + if (protectThreshold != vDom.getProtectThreshold()) { + Loggers.SRV_LOG.info("[DOM-UPDATE] dom: " + name + ",protectThreshold: " + protectThreshold + " -> " + vDom.getProtectThreshold()); + protectThreshold = vDom.getProtectThreshold(); + } + + if (useSpecifiedURL != vDom.isUseSpecifiedURL()) { + Loggers.SRV_LOG.info("[DOM-UPDATE] dom: " + name + ",useSpecifiedURL: " + useSpecifiedURL + " -> " + vDom.isUseSpecifiedURL()); + useSpecifiedURL = vDom.isUseSpecifiedURL(); + } + + if (resetWeight != vDom.getResetWeight().booleanValue()) { + Loggers.SRV_LOG.info("[DOM-UPDATE] dom: " + name + ",resetWeight: " + resetWeight + " -> " + vDom.getResetWeight()); + resetWeight = vDom.getResetWeight(); + } + + if (enableHealthCheck != vDom.getEnableHealthCheck().booleanValue()) { + Loggers.SRV_LOG.info("[DOM-UPDATE] dom: " + name + ", enableHealthCheck: " + enableHealthCheck + " -> " + vDom.getEnableHealthCheck()); + enableHealthCheck = vDom.getEnableHealthCheck(); + } + + if (enabled != vDom.getEnabled().booleanValue()) { + Loggers.SRV_LOG.info("[DOM-UPDATE] dom: " + name + ", enabled: " + enabled + " -> " + vDom.getEnabled()); + enabled = vDom.getEnabled(); + } + + updateOrAddCluster(vDom.getClusterMap().values()); + remvDeadClusters(this, vDom); + recalculateChecksum(); + } + + @Override + public String getChecksum() { + if (StringUtils.isEmpty(checksum)) { + recalculateChecksum(); + } + + return checksum; + } + + public synchronized void recalculateChecksum() { + List ips = allIPs(); + + StringBuilder ipsString = new StringBuilder(); + ipsString.append(getDomString()); + + Loggers.SRV_LOG.debug("dom to json: " + getDomString()); + + if (!CollectionUtils.isEmpty(ips)) { + Collections.sort(ips); + } + + for (IpAddress ip : ips) { + String string = ip.getIp() + ":" + ip.getPort() + "_" + ip.getWeight() + "_" + + ip.isValid() + "_" + ip.getClusterName(); + ipsString.append(string); + ipsString.append(","); + } + + try { + String result; + try { + MessageDigest md5 = MessageDigest.getInstance("MD5"); + result = new BigInteger(1, md5.digest((ipsString.toString()).getBytes(Charset.forName("UTF-8")))).toString(16); + } catch (Exception e) { + Loggers.SRV_LOG.error("VIPSRV-DOM", "error while calculating checksum(md5)", e); + result = RandomStringUtils.randomAscii(32); + } + + checksum = result; + } catch (Exception e) { + Loggers.SRV_LOG.error("VIPSRV-DOM", "error while calculating checksum(md5)", e); + checksum = RandomStringUtils.randomAscii(32); + } + } + + private void updateOrAddCluster(Collection clusters) { + for (Cluster cluster : clusters) { + Cluster oldCluster = clusterMap.get(cluster.getName()); + if (oldCluster != null) { + oldCluster.update(cluster); + } else { + cluster.init(); + clusterMap.put(cluster.getName(), cluster); + } + } + } + + private void remvDeadClusters(VirtualClusterDomain oldDom, VirtualClusterDomain newDom) { + Collection oldClusters = oldDom.getClusterMap().values(); + Collection newClusters = newDom.getClusterMap().values(); + List deadClusters = (List) CollectionUtils.subtract(oldClusters, newClusters); + for (Cluster cluster : deadClusters) { + oldDom.getClusterMap().remove(cluster.getName()); + + cluster.destroy(); + } + } + + @Override + public float getProtectThreshold() { + return protectThreshold; + } + + @Override + public void setProtectThreshold(float protectThreshold) { + this.protectThreshold = protectThreshold; + } + + public void addCluster(Cluster cluster) { + clusterMap.put(cluster.getName(), cluster); + } + + public void valid() { + if (!name.matches(DOMAIN_NAME_SYNTAX)) { + throw new IllegalArgumentException("dom name can only have these characters: 0-9a-zA-Z-._:, current: " + name); + } + + Map> map = new HashMap<>(clusterMap.size()); + for (Cluster cluster : clusterMap.values()) { + if (StringUtils.isEmpty(cluster.getSyncKey())) { + continue; + } + List list = map.get(cluster.getSyncKey()); + if (list == null) { + list = new ArrayList<>(); + map.put(cluster.getSyncKey(), list); + } + + list.add(cluster.getName()); + cluster.valid(); + } + + for (Map.Entry> entry : map.entrySet()) { + List list = entry.getValue(); + if (list.size() > 1) { + String msg = "clusters' config can not be the same: " + list; + Loggers.SRV_LOG.warn(msg); + throw new IllegalArgumentException(msg); + } + } + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/exception/NacosException.java b/naming/src/main/java/com/alibaba/nacos/naming/exception/NacosException.java new file mode 100644 index 00000000000..bf290c6b428 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/exception/NacosException.java @@ -0,0 +1,80 @@ +package com.alibaba.nacos.naming.exception; + +/** + * @author dungu.zpf + */ +public class NacosException extends Exception { + + private static final long serialVersionUID = 266495151581594848L; + + private int errorCode; + + private String errorMsg; + + public NacosException() { + super(); + } + + public NacosException(int errorCode) { + super(); + this.errorCode = errorCode; + } + + public NacosException(int errorCode, String errorMsg) { + super(errorMsg); + this.errorCode = errorCode; + this.errorMsg = errorMsg; + } + + public NacosException(int errorCode, String msg, Throwable cause) { + super(msg, cause); + this.errorCode = errorCode; + } + + public NacosException(int errorCode, Throwable cause) { + super(cause); + this.errorCode = errorCode; + } + + public int getErrorCode() { + return errorCode; + } + + public String getErrorMsg() { + return errorMsg; + } + + /** + * server error code, use http code 400 403 throw exception to user 500 502 + * 503 change ip and retry + */ + /** + * invalid param + */ + public static final int INVALID_PARAM = 400; + /** + * no right + */ + public static final int NO_RIGHT = 403; + /** + * not found + */ + public static final int NOT_FOUND = 404; + + /** + * conflict + */ + public static final int CONFLICT = 409; + /** + * server error + */ + public static final int SERVER_ERROR = 500; + /** + * bad gateway + */ + public static final int BAD_GATEWAY = 502; + /** + * over threshold + */ + public static final int OVER_THRESHOLD = 503; +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/exception/ResponseExceptionHandler.java b/naming/src/main/java/com/alibaba/nacos/naming/exception/ResponseExceptionHandler.java new file mode 100644 index 00000000000..bb729e34647 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/exception/ResponseExceptionHandler.java @@ -0,0 +1,30 @@ +package com.alibaba.nacos.naming.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +/** + * @author dungu.zpf + */ +@ControllerAdvice +public class ResponseExceptionHandler { + + @ExceptionHandler(NacosException.class) + private ResponseEntity handleNacosException(NacosException e) { + return ResponseEntity.status(e.getErrorCode()).body(e.getMessage()); + } + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleParameterError(IllegalArgumentException ex) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.getMessage()); + } + + @ExceptionHandler(MissingServletRequestParameterException.class) + public ResponseEntity handleMissingParams(MissingServletRequestParameterException ex) { + String name = ex.getParameterName(); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Parameter '" + name + "' is missing"); + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/AbstractHealthCheckConfig.java b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/AbstractHealthCheckConfig.java new file mode 100644 index 00000000000..faaf0fa0a74 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/AbstractHealthCheckConfig.java @@ -0,0 +1,327 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.healthcheck; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.annotation.JSONField; +import com.alibaba.fastjson.parser.DefaultJSONParser; +import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer; +import com.alibaba.fastjson.serializer.JSONSerializer; +import com.alibaba.fastjson.serializer.ObjectSerializer; +import com.alibaba.fastjson.serializer.SerializeWriter; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * @author nacos + */ +public abstract class AbstractHealthCheckConfig implements Cloneable { + + protected String type = "unknown"; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + /** + * Get copy of health check config + * + * @return Copy of health check config + * @throws CloneNotSupportedException + */ + @Override + public abstract AbstractHealthCheckConfig clone() throws CloneNotSupportedException; + + public static class Http extends AbstractHealthCheckConfig { + public static final String TYPE = "HTTP"; + public static final String HTTP_HEADER_SPLIT_STRING = "\\|"; + + private String path = StringUtils.EMPTY; + private String headers = StringUtils.EMPTY; + + private int expectedResponseCode = 200; + + public Http() { + this.type = TYPE; + } + + public int getExpectedResponseCode() { + return expectedResponseCode; + } + + public void setExpectedResponseCode(int expectedResponseCode) { + this.expectedResponseCode = expectedResponseCode; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getHeaders() { + return headers; + } + + public void setHeaders(String headers) { + this.headers = headers; + } + + @JSONField(serialize = false) + public Map getCustomHeaders() { + if (StringUtils.isBlank(headers)) { + return Collections.emptyMap(); + } + + Map headers = new HashMap(this.headers.split(HTTP_HEADER_SPLIT_STRING).length); + for (String s : this.headers.split(HTTP_HEADER_SPLIT_STRING)) { + String[] splits = s.split(":"); + if (splits.length != 2) { + continue; + } + + headers.put(StringUtils.trim(splits[0]), StringUtils.trim(splits[1])); + } + + return headers; + } + + @Override + public int hashCode() { + return Objects.hash(headers, path, expectedResponseCode); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Http)) { + return false; + } + + Http other = (Http) obj; + + if (!StringUtils.equals(type, other.getType())) { + return false; + } + + if (!StringUtils.equals(path, other.getPath())) { + return false; + } + if (!StringUtils.equals(headers, other.getHeaders())) { + return false; + } + return expectedResponseCode == other.getExpectedResponseCode(); + + } + + @SuppressFBWarnings("CN_IDIOM_NO_SUPER_CALL") + @Override + public AbstractHealthCheckConfig.Http clone() throws CloneNotSupportedException { + AbstractHealthCheckConfig.Http config = new AbstractHealthCheckConfig.Http(); + + config.setPath(this.path); + config.setHeaders(this.headers); + config.setType(this.type); + config.setExpectedResponseCode(this.expectedResponseCode); + + return config; + } + } + + public static class Tcp extends AbstractHealthCheckConfig { + public static final String TYPE = "TCP"; + + public Tcp() { + this.type = TYPE; + } + + @Override + public int hashCode() { + return Objects.hash(Tcp.TYPE); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof Tcp; + + } + + @SuppressFBWarnings("CN_IDIOM_NO_SUPER_CALL") + @Override + public AbstractHealthCheckConfig.Tcp clone() throws CloneNotSupportedException { + AbstractHealthCheckConfig.Tcp config = new AbstractHealthCheckConfig.Tcp(); + + config.setType(this.type); + + return config; + } + } + + public static class Mysql extends AbstractHealthCheckConfig { + public static final String TYPE = "MYSQL"; + + private String user; + private String pwd; + private String cmd; + + public Mysql() { + this.type = TYPE; + } + + public String getCmd() { + return cmd; + } + + public String getPwd() { + return pwd; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public void setCmd(String cmd) { + this.cmd = cmd; + } + + public void setPwd(String pwd) { + this.pwd = pwd; + } + + @Override + public int hashCode() { + return Objects.hash(user, pwd, cmd); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Mysql)) { + return false; + } + + Mysql other = (Mysql) obj; + + if (!StringUtils.equals(user, other.getUser())) { + return false; + } + + if (!StringUtils.equals(pwd, other.getPwd())) { + return false; + } + + return StringUtils.equals(cmd, other.getCmd()); + + } + + @SuppressFBWarnings("CN_IDIOM_NO_SUPER_CALL") + @Override + public AbstractHealthCheckConfig.Mysql clone() throws CloneNotSupportedException { + AbstractHealthCheckConfig.Mysql config = new AbstractHealthCheckConfig.Mysql(); + + config.setUser(this.user); + config.setPwd(this.pwd); + config.setCmd(this.cmd); + config.setType(this.type); + + return config; + } + } + + public static class JsonAdapter implements ObjectDeserializer, ObjectSerializer { + private static JsonAdapter INSTANCE = new JsonAdapter(); + + private JsonAdapter() { + } + + ; + + public static JsonAdapter getInstance() { + return INSTANCE; + } + + @Override + public T deserialze(DefaultJSONParser parser, Type type, Object fieldName) { + JSONObject jsonObj = (JSONObject) parser.parse(); + String checkType = jsonObj.getString("type"); + + if (StringUtils.equals(checkType, AbstractHealthCheckConfig.Http.TYPE)) { + return (T) JSON.parseObject(jsonObj.toJSONString(), AbstractHealthCheckConfig.Http.class); + } + + if (StringUtils.equals(checkType, AbstractHealthCheckConfig.Tcp.TYPE)) { + return (T) JSON.parseObject(jsonObj.toJSONString(), AbstractHealthCheckConfig.Tcp.class); + } + + if (StringUtils.equals(checkType, AbstractHealthCheckConfig.Mysql.TYPE)) { + return (T) JSON.parseObject(jsonObj.toJSONString(), AbstractHealthCheckConfig.Mysql.class); + } + + return null; + } + + @Override + public int getFastMatchToken() { + return 0; + } + + @Override + public void write(JSONSerializer jsonSerializer, Object o, Object o1, Type type, int i) throws IOException { + SerializeWriter writer = jsonSerializer.getWriter(); + if (o == null) { + writer.writeNull(); + return; + } + + AbstractHealthCheckConfig config = (AbstractHealthCheckConfig) o; + + writer.writeFieldValue(',', "type", config.getType()); + + if (StringUtils.equals(config.getType(), HealthCheckType.HTTP.name())) { + AbstractHealthCheckConfig.Http httpCheckConfig = (Http) config; + writer.writeFieldValue(',', "path", httpCheckConfig.getPath()); + writer.writeFieldValue(',', "headers", httpCheckConfig.getHeaders()); + } + + if (StringUtils.equals(config.getType(), HealthCheckType.TCP.name())) { + // nothing sepcial to handle + } + + if (StringUtils.equals(config.getType(), HealthCheckType.MYSQL.name())) { + AbstractHealthCheckConfig.Mysql mysqlCheckConfig = (Mysql) config; + writer.writeFieldValue(',', "user", mysqlCheckConfig.getUser()); + writer.writeFieldValue(',', "pwd", mysqlCheckConfig.getPwd()); + writer.writeFieldValue(',', "cmd", mysqlCheckConfig.getCmd()); + } + } + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/AbstractHealthCheckProcessor.java b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/AbstractHealthCheckProcessor.java new file mode 100644 index 00000000000..ccaebd19244 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/AbstractHealthCheckProcessor.java @@ -0,0 +1,328 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.healthcheck; + +import com.alibaba.fastjson.JSON; +import com.alibaba.nacos.naming.boot.RunningConfig; +import com.alibaba.nacos.naming.core.Cluster; +import com.alibaba.nacos.naming.core.DistroMapper; +import com.alibaba.nacos.naming.core.IpAddress; +import com.alibaba.nacos.naming.core.VirtualClusterDomain; +import com.alibaba.nacos.naming.misc.*; +import com.alibaba.nacos.naming.push.PushService; +import org.apache.commons.lang3.StringUtils; + +import java.net.HttpURLConnection; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.*; + +/** + * @author nacos + */ +public abstract class AbstractHealthCheckProcessor { + + private static final String HTTP_CHECK_MSG_PREFIX = "http:"; + + static class HealthCheckResult { + private String dom; + private IpAddress ipAddress; + + public HealthCheckResult(String dom, IpAddress ipAddress) { + this.dom = dom; + this.ipAddress = ipAddress; + } + + public String getDom() { + return dom; + } + + public void setDom(String dom) { + this.dom = dom; + } + + public IpAddress getIpAddress() { + return ipAddress; + } + + public void setIpAddress(IpAddress ipAddress) { + this.ipAddress = ipAddress; + } + } + + public static final int CONNECT_TIMEOUT_MS = 500; + private static LinkedBlockingDeque healthCheckResults = new LinkedBlockingDeque<>(1024 * 128); + + private void addResult(HealthCheckResult result) { + + if (!Switch.getIncrementalList().contains(result.getDom())) { + return; + } + + if (!healthCheckResults.offer(result)) { + Loggers.EVT_LOG.warn("HEALTH-CHECK-SYNC", "failed to add check result to queue, queue size: " + healthCheckResults.size()); + } + } + + private static ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r); + thread.setDaemon(true); + thread.setName("com.taobao.health-check.notifier"); + return thread; + } + }); + + + static { + executorService.schedule(new Runnable() { + @Override + public void run() { + List list = Arrays.asList(healthCheckResults.toArray()); + healthCheckResults.clear(); + + List sameSiteServers = NamingProxy.getSameSiteServers().get("sameSite"); + + if (sameSiteServers == null || sameSiteServers.size() <= 0 || !NamingProxy.getServers().contains(NetUtils.localIP())) { + return; + } + + for (String server : sameSiteServers) { + if (server.equals(NetUtils.localIP())) { + continue; + } + Map params = new HashMap<>(10); + params.put("result", JSON.toJSONString(list)); + Loggers.DEBUG_LOG.debug("HEALTH-SYNC", server, JSON.toJSONString(list)); + if (!server.contains(":")) { + server = server + ":" + RunningConfig.getServerPort(); + } + HttpClient.HttpResult httpResult = HttpClient.httpPost("http://" + server + + RunningConfig.getContextPath() + UtilsAndCommons.NACOS_NAMING_CONTEXT + + "/api/healthCheckResult", null, params); + + if (httpResult.code != HttpURLConnection.HTTP_OK) { + Loggers.EVT_LOG.warn("HEALTH-CHECK-SYNC", "failed to send result to " + server + + ", result: " + JSON.toJSONString(list)); + } + + } + + } + }, 500, TimeUnit.MILLISECONDS); + } + + /** + * Run check task for domain + * + * @param task check task + */ + public abstract void process(HealthCheckTask task); + + /** + * Get check task type, refer to enum HealthCheckType + * + * @return check type + */ + public abstract String getType(); + + public static final HttpHealthCheckProcessor HTTP_PROCESSOR = new HttpHealthCheckProcessor(); + public static final TcpSuperSenseProcessor TCP_PROCESSOR = new TcpSuperSenseProcessor(); + public static final MysqlHealthCheckProcessor MYSQL_PROCESSOR = new MysqlHealthCheckProcessor(); + + public static AbstractHealthCheckProcessor getProcessor(AbstractHealthCheckConfig config) { + if (config == null || StringUtils.isEmpty(config.getType())) { + throw new IllegalArgumentException("empty check type"); + } + + if (config.getType().equals(HTTP_PROCESSOR.getType())) { + return HTTP_PROCESSOR; + } + + if (config.getType().equals(TCP_PROCESSOR.getType())) { + return TCP_PROCESSOR; + } + + if (config.getType().equals(MYSQL_PROCESSOR.getType())) { + return MYSQL_PROCESSOR; + } + + throw new IllegalArgumentException("Unknown check type: " + config.getType()); + } + + protected boolean isHealthCheckEnabled(VirtualClusterDomain virtualClusterDomain) { + if (virtualClusterDomain.getEnableClientBeat()) { + return false; + } + + return virtualClusterDomain.getEnableHealthCheck(); + } + + protected void reEvaluateCheckRT(long checkRT, HealthCheckTask task, SwitchDomain.HealthParams params) { + task.setCheckRTLast(checkRT); + + if (checkRT > task.getCheckRTWorst()) { + task.setCheckRTWorst(checkRT); + } + + if (checkRT < task.getCheckRTBest()) { + task.setCheckRTBest(checkRT); + } + + checkRT = (long) ((params.getFactor() * task.getCheckRTNormalized()) + (1 - params.getFactor()) * checkRT); + + if (checkRT > params.getMax()) { + checkRT = params.getMax(); + } + + if (checkRT < params.getMin()) { + checkRT = params.getMin(); + } + + task.setCheckRTNormalized(checkRT); + } + + protected void checkOK(IpAddress ip, HealthCheckTask task, String msg) { + Cluster cluster = task.getCluster(); + + try { + if (!ip.isValid() || !ip.isMockValid()) { + if (ip.getOKCount().incrementAndGet() >= Switch.getCheckTimes()) { + if (cluster.responsible(ip)) { + ip.setValid(true); + ip.setMockValid(true); + ip.setInvalidType(IpAddress.InvalidType.VALID); + + VirtualClusterDomain vDom = (VirtualClusterDomain) cluster.getDom(); + vDom.setLastModifiedMillis(System.currentTimeMillis()); + + PushService.domChanged(vDom.getName()); + addResult(new HealthCheckResult(vDom.getName(), ip)); + + Loggers.EVT_LOG.info("{" + cluster.getDom().getName() + "} {POS} {IP-ENABLED} valid: " + + ip.getIp() + ":" + ip.getPort() + "@" + cluster.getName() + + ", region: " + DistroMapper.LOCALHOST_SITE + ", msg: " + msg); + } else { + if (!ip.isMockValid()) { + ip.setMockValid(true); + Loggers.EVT_LOG.info("{" + cluster.getDom().getName() + "} {PROBE} {IP-ENABLED} valid: " + + ip.getIp() + ":" + ip.getPort() + "@" + cluster.getName() + + ", region: " + DistroMapper.LOCALHOST_SITE + ", msg: " + msg); + } + } + } else { + Loggers.EVT_LOG.info("{" + cluster.getDom().getName() + "} {OTHER} " + + "{IP-ENABLED} pre-valid: " + ip.getIp() + ":" + ip.getPort() + "@" + + cluster.getName() + " in " + ip.getOKCount() + ", msg: " + msg); + } + } + } catch (Throwable t) { + Loggers.SRV_LOG.error("CHECK-OK", "error when close check task.", t); + } + + ip.getFailCount().set(0); + ip.setBeingChecked(false); + } + + protected void checkFail(IpAddress ip, HealthCheckTask task, String msg) { + Cluster cluster = task.getCluster(); + + try { + if (ip.isValid() || ip.isMockValid()) { + if (ip.getFailCount().incrementAndGet() >= Switch.getCheckTimes()) { + if (cluster.responsible(ip)) { + ip.setValid(false); + ip.setMockValid(false); + setInvalidType(ip, msg); + + VirtualClusterDomain vDom = (VirtualClusterDomain) cluster.getDom(); + vDom.setLastModifiedMillis(System.currentTimeMillis()); + addResult(new HealthCheckResult(vDom.getName(), ip)); + + PushService.domChanged(vDom.getName()); + + Loggers.EVT_LOG.info("{" + cluster.getDom().getName() + "} {POS} {IP-DISABLED} invalid: " + + ip.getIp() + ":" + ip.getPort() + "@" + cluster.getName() + + ", region: " + DistroMapper.LOCALHOST_SITE + ", msg: " + msg); + } else { + Loggers.EVT_LOG.info("{" + cluster.getDom().getName() + "} {PROBE} {IP-DISABLED} invalid: " + + ip.getIp() + ":" + ip.getPort() + "@" + cluster.getName() + + ", region: " + DistroMapper.LOCALHOST_SITE + ", msg: " + msg); + } + + } else { + Loggers.EVT_LOG.info("{" + cluster.getDom().getName() + "} {OTHER} " + + "{IP-DISABLED} pre-invalid: " + ip.getIp() + ":" + ip.getPort() + + "@" + cluster.getName() + " in " + ip.getFailCount() + ", msg: " + msg); + } + } + } catch (Throwable t) { + Loggers.SRV_LOG.error("CHECK-FAIL", "error when close check task.", t); + } + + ip.getOKCount().set(0); + + ip.setBeingChecked(false); + } + + private void setInvalidType(IpAddress ipAddress, String msg) { + if (msg.equals(HTTP_CHECK_MSG_PREFIX + IpAddress.InvalidType.HTTP_404)) { + ipAddress.setInvalidType(IpAddress.InvalidType.HTTP_404); + } else { + ipAddress.setInvalidType(IpAddress.InvalidType.NORMAL_INVALID); + } + } + + protected void checkFailNow(IpAddress ip, HealthCheckTask task, String msg) { + Cluster cluster = task.getCluster(); + try { + if (ip.isValid() || ip.isMockValid()) { + if (cluster.responsible(ip)) { + ip.setValid(false); + ip.setMockValid(false); + + setInvalidType(ip, msg); + + VirtualClusterDomain vDom = (VirtualClusterDomain) cluster.getDom(); + vDom.setLastModifiedMillis(System.currentTimeMillis()); + + PushService.domChanged(vDom.getName()); + addResult(new HealthCheckResult(vDom.getName(), ip)); + + Loggers.EVT_LOG.info("{" + cluster.getDom().getName() + "} {POS} {IP-DISABLED} invalid-now: " + + ip.getIp() + ":" + ip.getPort() + "@" + cluster.getName() + + ", region: " + DistroMapper.LOCALHOST_SITE + ", msg: " + msg); + } else { + if (ip.isMockValid()) { + ip.setMockValid(false); + Loggers.EVT_LOG.info("{" + cluster.getDom().getName() + "} {PROBE} {IP-DISABLED} invalid-now: " + + ip.getIp() + ":" + ip.getPort() + "@" + cluster.getName() + + ", region: " + DistroMapper.LOCALHOST_SITE + ", msg: " + msg); + } + + } + } + } catch (Throwable t) { + Loggers.SRV_LOG.error("CHECK-FAIL-NOW", "error when close check task.", t); + } + + ip.getOKCount().set(0); + ip.setBeingChecked(false); + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/ClientBeatCheckTask.java b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/ClientBeatCheckTask.java new file mode 100644 index 00000000000..811ce26b2ac --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/ClientBeatCheckTask.java @@ -0,0 +1,96 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.healthcheck; + +import com.alibaba.nacos.naming.boot.RunningConfig; +import com.alibaba.nacos.naming.core.DistroMapper; +import com.alibaba.nacos.naming.core.IpAddress; +import com.alibaba.nacos.naming.core.VirtualClusterDomain; +import com.alibaba.nacos.naming.misc.HttpClient; +import com.alibaba.nacos.naming.misc.Loggers; +import com.alibaba.nacos.naming.misc.UtilsAndCommons; +import com.alibaba.nacos.naming.push.PushService; +import com.alibaba.fastjson.JSON; + +import java.net.HttpURLConnection; +import java.util.List; + +/** + * @author dungu.zpf + */ +public class ClientBeatCheckTask implements Runnable { + private VirtualClusterDomain domain; + + public ClientBeatCheckTask(VirtualClusterDomain domain) { + this.domain = domain; + } + @Override + public void run() { + try { + if (!domain.getEnableClientBeat() || !DistroMapper.responsible(domain.getName())) { + return; + } + + List ipAddresses = domain.allIPs(); + + for (IpAddress ipAddress: ipAddresses) { + if (System.currentTimeMillis() - ipAddress.getLastBeat() > ClientBeatProcessor.CLIENT_BEAT_TIMEOUT) { + if (!ipAddress.isMarked()) { + if (ipAddress.isValid()) { + ipAddress.setValid(false); + Loggers.EVT_LOG.info("{" + ipAddress.getClusterName()+ "} {POS} {IP-DISABLED} valid: " + + ipAddress.getIp()+ ":" + ipAddress.getPort()+ "@" + ipAddress.getClusterName() + + ", region: " + DistroMapper.LOCALHOST_SITE + ", msg: " + "client timeout after " + + ClientBeatProcessor.CLIENT_BEAT_TIMEOUT + ", last beat: " + ipAddress.getLastBeat()); + PushService.domChanged(domain.getName()); + } + } + } + + if (System.currentTimeMillis() - ipAddress.getLastBeat() > domain.getIpDeleteTimeout()) { + // delete ip + if (domain.allIPs().size() > 1) { + Loggers.SRV_LOG.info("AUTO-DELETE-IP", "dom: " + domain.getName() + ", ip: " + JSON.toJSONString(ipAddress)); + deleteIP(ipAddress); + } + } + } + } catch (Exception e) { + Loggers.SRV_LOG.warn("Exception while processing client beat time out.", e); + } finally { + HealthCheckReactor.scheduleCheck(this); + } + + } + + private void deleteIP(IpAddress ipAddress) { + try { + String ipList = ipAddress.getIp() + ":" + ipAddress.getPort() + "_" + + ipAddress.getWeight() + "_" + ipAddress.getClusterName(); + String url = "http://127.0.0.1:" + RunningConfig.getServerPort() + RunningConfig.getContextPath() + + UtilsAndCommons.NACOS_NAMING_CONTEXT + "/api/remvIP4Dom?dom=" + + domain.getName() + "&ipList=" + ipList + "&token=" + domain.getToken(); + HttpClient.HttpResult result = HttpClient.httpGet(url, null, null); + if (result.code != HttpURLConnection.HTTP_OK) { + Loggers.SRV_LOG.error("IP-DEAD", "failed to delete ip automatically, ip: " + + ipAddress.toJSON() + ", caused " + result.content + ",resp code: " + result.code); + } + } catch (Exception e) { + Loggers.SRV_LOG.error("IP-DEAD", "failed to delete ip automatically, ip: " + ipAddress.toJSON(), e); + } + + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/ClientBeatProcessor.java b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/ClientBeatProcessor.java new file mode 100644 index 00000000000..1d71a9dd960 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/ClientBeatProcessor.java @@ -0,0 +1,95 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.healthcheck; + + +import com.alibaba.nacos.naming.core.*; +import com.alibaba.nacos.naming.misc.Loggers; +import com.alibaba.nacos.naming.push.PushService; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * @author dungu.zpf + */ +public class ClientBeatProcessor implements Runnable { + public static final long CLIENT_BEAT_TIMEOUT = TimeUnit.SECONDS.toMillis(15); + private RsInfo rsInfo; + private Domain domain; + + public RsInfo getRsInfo() { + return rsInfo; + } + + public void setRsInfo(RsInfo rsInfo) { + this.rsInfo = rsInfo; + } + + public Domain getDomain() { + return domain; + } + + public void setDomain(Domain domain) { + this.domain = domain; + } + + public ClientBeatProcessor() { + + } + + public String getType() { + return "CLIENT_BEAT"; + } + + public void process() { + VirtualClusterDomain virtualClusterDomain = (VirtualClusterDomain) domain; + if (!virtualClusterDomain.getEnableClientBeat()) { + return; + } + + Loggers.EVT_LOG.debug("client-beat", "processing beat: " + rsInfo.toString()); + + String ip = rsInfo.getIp(); + String clusterName = rsInfo.getCluster(); + int port = rsInfo.getPort(); + Cluster cluster = virtualClusterDomain.getClusterMap().get(clusterName); + List ipAddresses = cluster.allIPs(); + + boolean processed = false; + + for (IpAddress ipAddress: ipAddresses) { + if (ipAddress.getIp().equals(ip) && ipAddress.getPort() == port) { + processed = true; + ipAddress.setLastBeat(System.currentTimeMillis()); + if (!ipAddress.isMarked()) { + if (!ipAddress.isValid()) { + ipAddress.setValid(true); + Loggers.EVT_LOG.info("{" + cluster.getDom().getName() + "} {POS} {IP-ENABLED} valid: " + + ip+ ":" + port+ "@" + cluster.getName() + + ", region: " + DistroMapper.LOCALHOST_SITE + ", msg: " + "client beat ok"); + PushService.domChanged(domain.getName()); + } + } + } + } + } + + @Override + public void run() { + process(); + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/HealthCheckMode.java b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/HealthCheckMode.java new file mode 100644 index 00000000000..fe109fbe4d4 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/HealthCheckMode.java @@ -0,0 +1,21 @@ +package com.alibaba.nacos.naming.healthcheck; + +/** + * Health check mode + * + * @author dungu.zpf + */ +public enum HealthCheckMode { + /** + * Health check sent from server. + */ + server, + /** + * Health check sent from client. + */ + client, + /** + * Health check disabled. + */ + none +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/HealthCheckReactor.java b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/HealthCheckReactor.java new file mode 100644 index 00000000000..dd19664c980 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/HealthCheckReactor.java @@ -0,0 +1,48 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.healthcheck; + +import java.util.concurrent.*; + +/** + * @author nacos + */ +public class HealthCheckReactor { + private static final ScheduledExecutorService EXECUTOR = Executors + .newScheduledThreadPool(Runtime.getRuntime().availableProcessors() / 2, new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r); + thread.setDaemon(true); + thread.setName("com.alibaba.nacos.naming.health"); + return thread; + } + }); + + public static ScheduledFuture scheduleCheck(HealthCheckTask task) { + task.setStartTime(System.currentTimeMillis()); + + return EXECUTOR.schedule(task, task.getCheckRTNormalized(), TimeUnit.MILLISECONDS); + } + + public static ScheduledFuture scheduleCheck(ClientBeatCheckTask task) { + return EXECUTOR.schedule(task, 5000, TimeUnit.MILLISECONDS); + } + + public static ScheduledFuture scheduleNow(Runnable task) { + return EXECUTOR.schedule(task, 0, TimeUnit.MILLISECONDS); + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/HealthCheckStatus.java b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/HealthCheckStatus.java new file mode 100644 index 00000000000..ac948244762 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/HealthCheckStatus.java @@ -0,0 +1,81 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.healthcheck; + + +import com.alibaba.nacos.naming.core.Cluster; +import com.alibaba.nacos.naming.core.Domain; +import com.alibaba.nacos.naming.core.IpAddress; +import com.alibaba.nacos.naming.misc.Loggers; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author nacos + */ +public class HealthCheckStatus { + public AtomicBoolean isBeingChecked = new AtomicBoolean(false); + public AtomicInteger checkFailCount = new AtomicInteger(0); + public AtomicInteger checkOKCount = new AtomicInteger(0); + public long checkRT = -1L; + + private static ConcurrentMap statusMap = + new ConcurrentHashMap(); + + public static void reset(IpAddress ip) { + statusMap.put(buildKey(ip), new HealthCheckStatus()); + } + + public static HealthCheckStatus get(IpAddress ip) { + String key = buildKey(ip); + + if (!statusMap.containsKey(key)) { + statusMap.putIfAbsent(key, new HealthCheckStatus()); + } + + return statusMap.get(key); + } + + public static void remv(IpAddress ip) { + statusMap.remove(buildKey(ip)); + } + + private static String buildKey(IpAddress ip) { + try { + Cluster cluster = ip.getCluster(); + Domain domain = cluster.getDom(); + + if (domain == null) { + Loggers.SRV_LOG.warn("BUILD-KEY", "domain is null, ip: " + ip.toIPAddr()); + return ip.getDefaultKey(); + } + + String clusterName = cluster.getName(); + String dom = domain.getName(); + String datumKey = ip.getDatumKey(); + return dom + ":" + + clusterName + ":" + + datumKey; + } catch (Throwable e) { + Loggers.SRV_LOG.error("BUILD-KEY", "Exception while set rt, ip " + ip.toJSON(), e); + } + + return ip.getDefaultKey(); + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/HealthCheckTask.java b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/HealthCheckTask.java new file mode 100644 index 00000000000..ea80a421e90 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/HealthCheckTask.java @@ -0,0 +1,159 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.healthcheck; + +import com.alibaba.nacos.naming.core.Cluster; +import com.alibaba.nacos.naming.core.DistroMapper; +import com.alibaba.nacos.naming.core.VirtualClusterDomain; +import com.alibaba.nacos.naming.misc.Loggers; +import com.alibaba.nacos.naming.misc.Switch; +import org.apache.commons.lang3.RandomUtils; + +/** + * @author nacos + */ +public class HealthCheckTask implements Runnable { + + private Cluster cluster; + + private long checkRTNormalized = -1; + private long checkRTBest = -1; + private long checkRTWorst= -1; + + private long checkRTLast = -1; + private long checkRTLastLast = -1; + + private long startTime; + + private volatile boolean cancelled = false; + + public HealthCheckTask(Cluster cluster) { + this.cluster = cluster; + initCheckRT(); + } + + public void initCheckRT() { + // first check time delay + checkRTNormalized = 2000 + RandomUtils.nextInt(0, Switch.getTcpHealthParams().getMax()); + + checkRTBest = Long.MAX_VALUE; + checkRTWorst = 0L; + } + + @Override + public void run() { + AbstractHealthCheckProcessor processor = AbstractHealthCheckProcessor.getProcessor(cluster.getHealthChecker()); + + try { + if (DistroMapper.responsible(cluster.getDom().getName())) { + processor.process(this); + Loggers.EVT_LOG.debug("HEALTH-CHECK", "schedule health check task: " + cluster.getDom().getName()); + } + } catch (Throwable e) { + Loggers.SRV_LOG.error("VIPSRV-HEALTH-CHECK", "error while process health check for " + cluster.getDom().getName() + ":" + cluster.getName(), e); + } finally { + if (!cancelled) { + HealthCheckReactor.scheduleCheck(this); + + // worst == 0 means never checked + if (this.getCheckRTWorst() > 0 + && Switch.isHealthCheckEnabled(cluster.getDom().getName()) + && DistroMapper.responsible(cluster.getDom().getName())) { + // TLog doesn't support float so we must convert it into long + long diff = ((this.getCheckRTLast() - this.getCheckRTLastLast()) * 10000) + / this.getCheckRTLastLast(); + + this.setCheckRTLastLast(this.getCheckRTLast()); + + Cluster cluster = this.getCluster(); + if (((VirtualClusterDomain)cluster.getDom()).getEnableHealthCheck()) { + Loggers.CHECK_RT.info(cluster.getDom().getName() + ":" + cluster.getName() + + "@" + processor.getType() + + "->normalized: " + this.getCheckRTNormalized() + + ", worst: " + this.getCheckRTWorst() + + ", best: " + this.getCheckRTBest() + + ", last: " + this.getCheckRTLast() + + ", diff: " + diff); + } + } + } + } + } + + public Cluster getCluster() { + return cluster; + } + + public void setCluster(Cluster cluster) { + this.cluster = cluster; + } + + public long getCheckRTNormalized() { + return checkRTNormalized; + } + + public long getCheckRTBest() { + return checkRTBest; + } + + public long getCheckRTWorst() { + return checkRTWorst; + } + + public void setCheckRTWorst(long checkRTWorst) { + this.checkRTWorst = checkRTWorst; + } + + public void setCheckRTBest(long checkRTBest) { + this.checkRTBest = checkRTBest; + } + + public void setCheckRTNormalized(long checkRTNormalized) { + this.checkRTNormalized = checkRTNormalized; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + public long getStartTime() { + return startTime; + } + + public void setStartTime(long startTime) { + this.startTime = startTime; + } + + public long getCheckRTLast() { + return checkRTLast; + } + + public void setCheckRTLast(long checkRTLast) { + this.checkRTLast = checkRTLast; + } + + public long getCheckRTLastLast() { + return checkRTLastLast; + } + + public void setCheckRTLastLast(long checkRTLastLast) { + this.checkRTLastLast = checkRTLastLast; + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/HealthCheckType.java b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/HealthCheckType.java new file mode 100644 index 00000000000..fdd3c51e85e --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/HealthCheckType.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.healthcheck; + +/** + * @author dungu.zpf + */ +public enum HealthCheckType { + /** + * TCP type + */ + TCP, + /** + * HTTP type + */ + HTTP, + /** + * MySQL type + */ + MYSQL +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/HttpHealthCheckProcessor.java b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/HttpHealthCheckProcessor.java new file mode 100644 index 00000000000..25ad6985ea2 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/HttpHealthCheckProcessor.java @@ -0,0 +1,200 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.healthcheck; + +import com.alibaba.nacos.naming.core.Cluster; +import com.alibaba.nacos.naming.core.IpAddress; +import com.alibaba.nacos.naming.core.VirtualClusterDomain; +import com.alibaba.nacos.naming.misc.Switch; +import com.ning.http.client.AsyncCompletionHandler; +import com.ning.http.client.AsyncHttpClient; +import com.ning.http.client.AsyncHttpClientConfig; +import com.ning.http.client.Response; +import io.netty.channel.ConnectTimeoutException; +import org.apache.commons.collections.CollectionUtils; + +import java.net.ConnectException; +import java.net.HttpURLConnection; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeoutException; + +import static com.alibaba.nacos.naming.misc.Loggers.SRV_LOG; + +/** + * HTTP health check processor + * + * @author xuanyin.zy + */ +public class HttpHealthCheckProcessor extends AbstractHealthCheckProcessor { + private static AsyncHttpClient asyncHttpClient; + + public HttpHealthCheckProcessor() { + } + + static { + try { + AsyncHttpClientConfig.Builder builder = new AsyncHttpClientConfig.Builder(); + + builder.setMaximumConnectionsTotal(-1); + builder.setMaximumConnectionsPerHost(-1); + builder.setAllowPoolingConnection(false); + builder.setFollowRedirects(false); + builder.setIdleConnectionTimeoutInMs(CONNECT_TIMEOUT_MS); + builder.setConnectionTimeoutInMs(CONNECT_TIMEOUT_MS); + builder.setCompressionEnabled(false); + builder.setIOThreadMultiplier(1); + builder.setMaxRequestRetry(0); + builder.setUserAgent("VIPServer"); + asyncHttpClient = new AsyncHttpClient(builder.build()); + } catch (Throwable e) { + SRV_LOG.error("VIPSRV-HEALTH-CHECK", "Error while constructing HTTP asynchronous client, " + e.toString(), e); + } + } + + @Override + public String getType() { + return "HTTP"; + } + + @Override + public void process(HealthCheckTask task) { + List ips = task.getCluster().allIPs(); + if (CollectionUtils.isEmpty(ips)) { + return; + } + + VirtualClusterDomain virtualClusterDomain = (VirtualClusterDomain) task.getCluster().getDom(); + + if (!isHealthCheckEnabled(virtualClusterDomain)) { + return; + } + + Cluster cluster = task.getCluster(); + + for (IpAddress ip : ips) { + try { + + if (ip.isMarked()) { + if (SRV_LOG.isDebugEnabled()) { + SRV_LOG.debug("http check, ip is marked as to skip health check, ip:" + ip.getIp()); + } + continue; + } + + if (!ip.markChecking()) { + SRV_LOG.warn("http check started before last one finished, dom: " + + task.getCluster().getDom().getName() + ":" + + task.getCluster().getName() + ":" + + ip.getIp()); + + reEvaluateCheckRT(task.getCheckRTNormalized() * 2, task, Switch.getHttpHealthParams()); + continue; + } + + AbstractHealthCheckConfig.Http healthChecker = (AbstractHealthCheckConfig.Http) cluster.getHealthChecker(); + + int ckPort = cluster.isUseIPPort4Check() ? ip.getPort() : cluster.getDefCkport(); + URL host = new URL("http://" + ip.getIp() + ":" + ckPort); + URL target = new URL(host, healthChecker.getPath()); + + AsyncHttpClient.BoundRequestBuilder builder = asyncHttpClient.prepareGet(target.toString()); + Map customHeaders = healthChecker.getCustomHeaders(); + for (Map.Entry entry : customHeaders.entrySet()) { + if ("Host".equals(entry.getKey())) { + builder.setVirtualHost(entry.getValue()); + continue; + } + + builder.setHeader(entry.getKey(), entry.getValue()); + } + + builder.execute(new HttpHealthCheckCallback(ip, task)); + } catch (Throwable e) { + ip.setCheckRT(Switch.getHttpHealthParams().getMax()); + checkFail(ip, task, "http:error:" + e.getMessage()); + reEvaluateCheckRT(Switch.getHttpHealthParams().getMax(), task, Switch.getHttpHealthParams()); + } + } + } + + private class HttpHealthCheckCallback extends AsyncCompletionHandler { + private IpAddress ip; + private HealthCheckTask task; + + private long startTime = System.currentTimeMillis(); + + public HttpHealthCheckCallback(IpAddress ip, HealthCheckTask task) { + this.ip = ip; + this.task = task; + } + + @Override + public Integer onCompleted(Response response) throws Exception { + ip.setCheckRT(System.currentTimeMillis() - startTime); + + int httpCode = response.getStatusCode(); + if (HttpURLConnection.HTTP_OK == httpCode) { + checkOK(ip, task, "http:" + httpCode); + reEvaluateCheckRT(System.currentTimeMillis() - startTime, task, Switch.getHttpHealthParams()); + } else if (HttpURLConnection.HTTP_UNAVAILABLE == httpCode || HttpURLConnection.HTTP_MOVED_TEMP == httpCode) { + // server is busy, need verification later + checkFail(ip, task, "http:" + httpCode); + reEvaluateCheckRT(task.getCheckRTNormalized() * 2, task, Switch.getHttpHealthParams()); + } else { + //probably means the state files has been removed by administrator + checkFailNow(ip, task, "http:" + httpCode); + reEvaluateCheckRT(Switch.getHttpHealthParams().getMax(), task, Switch.getHttpHealthParams()); + } + + return httpCode; + } + + @Override + public void onThrowable(Throwable t) { + ip.setCheckRT(System.currentTimeMillis() - startTime); + + Throwable cause = t; + int maxStackDepth = 50; + for (int deepth = 0; deepth < maxStackDepth && cause != null; deepth++) { + if (cause instanceof SocketTimeoutException + || cause instanceof ConnectTimeoutException + || cause instanceof org.jboss.netty.channel.ConnectTimeoutException + || cause instanceof TimeoutException + || cause.getCause() instanceof TimeoutException) { + + checkFail(ip, task, "http:timeout:" + cause.getMessage()); + reEvaluateCheckRT(task.getCheckRTNormalized() * 2, task, Switch.getHttpHealthParams()); + + return; + } + + cause = cause.getCause(); + } + + // connection error, probably not reachable + if (t instanceof ConnectException) { + checkFailNow(ip, task, "http:unable2connect:" + t.getMessage()); + reEvaluateCheckRT(Switch.getHttpHealthParams().getMax(), task, Switch.getHttpHealthParams()); + } else { + checkFail(ip, task, "http:error:" + t.getMessage()); + reEvaluateCheckRT(Switch.getHttpHealthParams().getMax(), task, Switch.getHttpHealthParams()); + } + } + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/MysqlHealthCheckProcessor.java b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/MysqlHealthCheckProcessor.java new file mode 100644 index 00000000000..3717cf83014 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/MysqlHealthCheckProcessor.java @@ -0,0 +1,210 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.healthcheck; + +import com.alibaba.nacos.naming.core.Cluster; +import com.alibaba.nacos.naming.core.IpAddress; +import com.alibaba.nacos.naming.core.VirtualClusterDomain; +import com.alibaba.nacos.naming.misc.Loggers; +import com.alibaba.nacos.naming.misc.Switch; +import com.mysql.jdbc.jdbc2.optional.MysqlDataSource; +import io.netty.channel.ConnectTimeoutException; +import org.apache.commons.collections.CollectionUtils; + +import java.net.SocketTimeoutException; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.List; +import java.util.concurrent.*; + +import static com.alibaba.nacos.naming.misc.Loggers.SRV_LOG; + +/** + * MYSQL health check processor + * + * @author nacos + */ +public class MysqlHealthCheckProcessor extends AbstractHealthCheckProcessor { + + private static final String CHECK_MYSQL_MASTER_SQL = "show global variables where variable_name='read_only'"; + private static final String MYSQL_SLAVE_READONLY = "ON"; + + private static ConcurrentMap CONNECTION_POOL + = new ConcurrentHashMap(); + + private static ExecutorService EXECUTOR + = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() / 2, + new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r); + thread.setDaemon(true); + thread.setName("com.nacos.mysql.checker"); + return thread; + } + } + ); + + public MysqlHealthCheckProcessor() { + } + + @Override + public String getType() { + return "MYSQL"; + } + + @Override + public void process(HealthCheckTask task) { + List ips = task.getCluster().allIPs(); + + SRV_LOG.debug("mysql check, ips:" + ips); + if (CollectionUtils.isEmpty(ips)) { + return; + } + + VirtualClusterDomain virtualClusterDomain = (VirtualClusterDomain) task.getCluster().getDom(); + + if (!isHealthCheckEnabled(virtualClusterDomain)) { + return; + } + + for (IpAddress ip : ips) { + try { + + if (ip.isMarked()) { + if (SRV_LOG.isDebugEnabled()) { + SRV_LOG.debug("mysql check, ip is marked as to skip health check, ip:" + ip.getIp()); + } + continue; + } + + if (!ip.markChecking()) { + SRV_LOG.warn("mysql check started before last one finished, dom: " + + task.getCluster().getDom().getName() + ":" + + task.getCluster().getName() + ":" + + ip.getIp()); + + reEvaluateCheckRT(task.getCheckRTNormalized() * 2, task, Switch.getMysqlHealthParams()); + continue; + } + + EXECUTOR.execute(new MysqlCheckTask(ip, task)); + } catch (Exception e) { + ip.setCheckRT(Switch.getMysqlHealthParams().getMax()); + checkFail(ip, task, "mysql:error:" + e.getMessage()); + reEvaluateCheckRT(Switch.getMysqlHealthParams().getMax(), task, Switch.getMysqlHealthParams()); + } + } + } + + private class MysqlCheckTask implements Runnable { + private IpAddress ip; + private HealthCheckTask task; + private long startTime = System.currentTimeMillis(); + + public MysqlCheckTask(IpAddress ip, HealthCheckTask task) { + this.ip = ip; + this.task = task; + } + + @Override + public void run() { + + Statement statement = null; + ResultSet resultSet = null; + + try {; + Cluster cluster = task.getCluster(); + String key = cluster.getDom().getName() + ":" + cluster.getName() + ":" + ip.getIp() + ":" + ip.getPort(); + Connection connection = CONNECTION_POOL.get(key); + AbstractHealthCheckConfig.Mysql config = (AbstractHealthCheckConfig.Mysql) cluster.getHealthChecker(); + + if (connection == null || connection.isClosed()) { + MysqlDataSource dataSource = new MysqlDataSource(); + dataSource.setConnectTimeout(CONNECT_TIMEOUT_MS); + dataSource.setSocketTimeout(CONNECT_TIMEOUT_MS); + dataSource.setUser(config.getUser()); + dataSource.setPassword(config.getPwd()); + dataSource.setLoginTimeout(1); + + dataSource.setServerName(ip.getIp()); + dataSource.setPort(ip.getPort()); + + connection = dataSource.getConnection(); + CONNECTION_POOL.put(key, connection); + } + + statement = connection.createStatement(); + statement.setQueryTimeout(1); + + resultSet = statement.executeQuery(config.getCmd()); + int resultColumnIndex = 2; + + if (CHECK_MYSQL_MASTER_SQL.equals(config.getCmd())) { + resultSet.next(); + if (MYSQL_SLAVE_READONLY.equals(resultSet.getString(resultColumnIndex))) { + throw new IllegalStateException("current node is slave!"); + } + } + + checkOK(ip, task, "mysql:+ok"); + reEvaluateCheckRT(System.currentTimeMillis() - startTime, task, Switch.getMysqlHealthParams()); + } catch (SQLException e) { + // fail immediately + checkFailNow(ip, task, "mysql:" + e.getMessage()); + reEvaluateCheckRT(Switch.getHttpHealthParams().getMax(), task, Switch.getMysqlHealthParams()); + } catch (Throwable t) { + Throwable cause = t; + int maxStackDepth = 50; + for (int deepth = 0; deepth < maxStackDepth && cause != null; deepth++) { + if (cause instanceof SocketTimeoutException + || cause instanceof ConnectTimeoutException + || cause instanceof TimeoutException + || cause.getCause() instanceof TimeoutException) { + + checkFail(ip, task, "mysql:timeout:" + cause.getMessage()); + reEvaluateCheckRT(task.getCheckRTNormalized() * 2, task, Switch.getMysqlHealthParams()); + return; + } + + cause = cause.getCause(); + } + + // connection error, probably not reachable + checkFail(ip, task, "mysql:error:" + t.getMessage()); + reEvaluateCheckRT(Switch.getMysqlHealthParams().getMax(), task, Switch.getMysqlHealthParams()); + } finally { + ip.setCheckRT(System.currentTimeMillis() - startTime); + if (statement!=null) { + try { + statement.close(); + } catch (SQLException e) { + Loggers.SRV_LOG.error("MYSQL-CHECK", "failed to close statement:" + statement, e); + } + } + if (resultSet != null) { + try { + resultSet.close(); + } catch (SQLException e) { + Loggers.SRV_LOG.error("MYSQL-CHECK", "failed to close resultSet:" + resultSet, e); + } + } + } + } + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/RsInfo.java b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/RsInfo.java new file mode 100644 index 00000000000..8043d575b2a --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/RsInfo.java @@ -0,0 +1,120 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.healthcheck; + +import com.alibaba.fastjson.JSON; + +/** + * Metrics info of server + * + * @author nacos + */ +public class RsInfo { + private double load; + private double cpu; + private double rt; + private double qps; + private double mem; + private int port; + private String ip; + private String dom; + private String ak; + private String cluster; + + public String getDom() { + return dom; + } + + public void setDom(String dom) { + this.dom = dom; + } + + public String getAk() { + return ak; + } + + public void setAk(String ak) { + this.ak = ak; + } + + public String getCluster() { + return cluster; + } + + public void setCluster(String cluster) { + this.cluster = cluster; + } + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public double getLoad() { + return load; + } + + public void setLoad(double load) { + this.load = load; + } + + public double getCpu() { + return cpu; + } + + public void setCpu(double cpu) { + this.cpu = cpu; + } + + public double getRt() { + return rt; + } + + public void setRt(double rt) { + this.rt = rt; + } + + public double getQps() { + return qps; + } + + public void setQps(double qps) { + this.qps = qps; + } + + public double getMem() { + return mem; + } + + public void setMem(double mem) { + this.mem = mem; + } + + public String toString() { + return JSON.toJSONString(this); + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/TcpSuperSenseProcessor.java b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/TcpSuperSenseProcessor.java new file mode 100644 index 00000000000..c5b6c6d263c --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/healthcheck/TcpSuperSenseProcessor.java @@ -0,0 +1,420 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.healthcheck; + +import com.alibaba.nacos.naming.core.Cluster; +import com.alibaba.nacos.naming.core.IpAddress; +import com.alibaba.nacos.naming.core.VirtualClusterDomain; +import com.alibaba.nacos.naming.misc.Loggers; +import com.alibaba.nacos.naming.misc.Switch; +import org.apache.commons.collections.CollectionUtils; + +import java.net.ConnectException; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; +import java.util.*; +import java.util.concurrent.*; + +import static com.alibaba.nacos.naming.misc.Loggers.SRV_LOG; + +/** + * TCP health check processor + * + * @author nacos + */ +public class TcpSuperSenseProcessor extends AbstractHealthCheckProcessor implements Runnable { + + private Map keyMap = new ConcurrentHashMap<>(); + + private BlockingQueue taskQueue = new LinkedBlockingQueue(); + + /** + * this value has been carefully tuned, do not modify unless you're confident + */ + public static final int NIO_THREAD_COUNT = Runtime.getRuntime().availableProcessors() / 2; + + /** + * because some hosts doesn't support keep-alive connections, disabled temporarily + */ + public static final long TCP_KEEP_ALIVE_MILLIS = 0; + + private static ScheduledExecutorService TCP_CHECK_EXECUTOR + = new ScheduledThreadPoolExecutor(1, new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setName("nacos.naming.tcp.check.worker"); + t.setDaemon(true); + return t; + } + }); + + private static ScheduledExecutorService NIO_EXECUTOR + = Executors.newScheduledThreadPool(NIO_THREAD_COUNT, + new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r); + thread.setDaemon(true); + thread.setName("nacos.supersense.checker"); + return thread; + } + } + ); + + private Selector selector; + + public TcpSuperSenseProcessor() { + try { + selector = Selector.open(); + + TCP_CHECK_EXECUTOR.submit(this); + + } catch (Exception e) { + throw new IllegalStateException("Error while initializing SuperSense(TM)."); + } + } + + @Override + public void process(HealthCheckTask task) { + List ips = task.getCluster().allIPs(); + + if (CollectionUtils.isEmpty(ips)) { + return; + } + VirtualClusterDomain virtualClusterDomain = (VirtualClusterDomain) task.getCluster().getDom(); + + if (!isHealthCheckEnabled(virtualClusterDomain)) { + return; + } + + for (IpAddress ip : ips) { + + if (ip.isMarked()) { + if (SRV_LOG.isDebugEnabled()) { + SRV_LOG.debug("tcp check, ip is marked as to skip health check, ip:" + ip.getIp()); + } + continue; + } + + if (!ip.markChecking()) { + SRV_LOG.warn("tcp check started before last one finished, dom: " + + task.getCluster().getDom().getName() + ":" + + task.getCluster().getName() + ":" + + ip.getIp() + ":" + + ip.getPort()); + + reEvaluateCheckRT(task.getCheckRTNormalized() * 2, task, Switch.getTcpHealthParams()); + continue; + } + + Beat beat = new Beat(ip, task); + taskQueue.add(beat); + } + +// selector.wakeup(); + } + + private void processTask() throws Exception { + Collection> tasks = new LinkedList>(); + do { + Beat beat = taskQueue.poll(AbstractHealthCheckProcessor.CONNECT_TIMEOUT_MS / 2, TimeUnit.MILLISECONDS); + if (beat == null) { + return; + } + + tasks.add(new TaskProcessor(beat)); + } while (taskQueue.size() > 0 && tasks.size() < NIO_THREAD_COUNT * 64); + + for (Future f : NIO_EXECUTOR.invokeAll(tasks)) { + f.get(); + } + } + + @Override + public void run() { + while (true) { + try { + processTask(); + + int readyCount = selector.selectNow(); + if (readyCount <= 0) { + continue; + } + + Iterator iter = selector.selectedKeys().iterator(); + while (iter.hasNext()) { + SelectionKey key = iter.next(); + iter.remove(); + + NIO_EXECUTOR.execute(new PostProcessor(key)); + } + } catch (Throwable e) { + SRV_LOG.error("VIPSRV-HEALTH-CHECK", "error while processing NIO task", e); + } + } + } + + public static class PostProcessor implements Runnable { + SelectionKey key; + + public PostProcessor(SelectionKey key) { + this.key = key; + } + + @Override + public void run() { + Beat beat = (Beat) key.attachment(); + SocketChannel channel = (SocketChannel) key.channel(); + try { + if (!beat.isValid()) { + //invalid beat means this server is no longer responsible for the current dom + key.cancel(); + key.channel().close(); + + beat.finishCheck(); + return; + } + + if (key.isValid() && key.isConnectable()) { + //connected + channel.finishConnect(); + beat.finishCheck(true, false, System.currentTimeMillis() - beat.getTask().getStartTime(), "tcp:ok+"); + } + + if (key.isValid() && key.isReadable()) { + //disconnected + ByteBuffer buffer = ByteBuffer.allocate(128); + if (channel.read(buffer) == -1) { + key.cancel(); + key.channel().close(); + } else { + // not terminate request, ignore + } + } + } catch (ConnectException e) { + // unable to connect, possibly port not opened + beat.finishCheck(false, true, Switch.getTcpHealthParams().getMax(), "tcp:unable2connect:" + e.getMessage()); + } catch (Exception e) { + beat.finishCheck(false, false, Switch.getTcpHealthParams().getMax(), "tcp:error:" + e.getMessage()); + + try { + key.cancel(); + key.channel().close(); + } catch (Exception ignore) { + } + } + } + } + + private class Beat { + IpAddress ip; + + HealthCheckTask task; + + long startTime = System.currentTimeMillis(); + + Beat(IpAddress ip, HealthCheckTask task) { + this.ip = ip; + this.task = task; + } + + public void setStartTime(long time) { + startTime = time; + } + + public long getStartTime() { + return startTime; + } + + public IpAddress getIp() { + return ip; + } + + public HealthCheckTask getTask() { + return task; + } + + public boolean isValid() { + return System.currentTimeMillis() - startTime < TimeUnit.SECONDS.toMillis(30L); + } + + /** + * finish check only, no ip state will be changed + */ + public void finishCheck() { + ip.setBeingChecked(false); + } + + public void finishCheck(boolean success, boolean now, long rt, String msg) { + ip.setCheckRT(System.currentTimeMillis() - startTime); + + if (success) { + checkOK(ip, task, msg); + } else { + if (now) { + checkFailNow(ip, task, msg); + } else { + checkFail(ip, task, msg); + } + + keyMap.remove(task.toString()); + } + + reEvaluateCheckRT(rt, task, Switch.getTcpHealthParams()); + } + + @Override + public String toString() { + return task.getCluster().getDom().getName() + ":" + + task.getCluster().getName() + ":" + + ip.getIp() + ":" + + ip.getPort(); + } + + @Override + public int hashCode() { + return Objects.hash(ip.toJSON()); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof Beat)) { + return false; + } + + return this.toString().equals(obj.toString()); + } + } + + private static class BeatKey { + public SelectionKey key; + public long birthTime; + + public BeatKey(SelectionKey key) { + this.key = key; + this.birthTime = System.currentTimeMillis(); + } + } + + private static class TimeOutTask implements Runnable { + SelectionKey key; + + public TimeOutTask(SelectionKey key) { + this.key = key; + } + + @Override + public void run() { + if (key != null && key.isValid()) { + SocketChannel channel = (SocketChannel) key.channel(); + Beat beat = (Beat) key.attachment(); + + if (channel.isConnected()) { + return; + } + + try { + channel.finishConnect(); + } catch (Exception ignore) { + } + + try { + beat.finishCheck(false, false, beat.getTask().getCheckRTNormalized() * 2, "tcp:timeout"); + + key.cancel(); + key.channel().close(); + } catch (Exception ignore) { + } + } + } + } + + private class TaskProcessor implements Callable { + + private static final int MAX_WAIT_TIME_MILLISECONDS = 500; + Beat beat = null; + + public TaskProcessor(Beat beat) { + this.beat = beat; + } + + @Override + public Void call() { + long waited = System.currentTimeMillis() - beat.getStartTime(); + if (waited > MAX_WAIT_TIME_MILLISECONDS) { + Loggers.SRV_LOG.warn("beat task waited too long: " + waited + "ms"); + } + + SocketChannel channel = null; + try { + IpAddress ipAddress = beat.getIp(); + Cluster cluster = beat.getTask().getCluster(); + + BeatKey beatKey = keyMap.get(beat.toString()); + if (beatKey != null && beatKey.key.isValid()) { + if (System.currentTimeMillis() - beatKey.birthTime < TCP_KEEP_ALIVE_MILLIS) { + ipAddress.setBeingChecked(false); + return null; + } + + beatKey.key.cancel(); + beatKey.key.channel().close(); + } + + channel = SocketChannel.open(); + channel.configureBlocking(false); + // only by setting this can we make the socket close event asynchronous + channel.socket().setSoLinger(false, -1); + channel.socket().setReuseAddress(true); + channel.socket().setKeepAlive(true); + channel.socket().setTcpNoDelay(true); + + int port = cluster.isUseIPPort4Check() ? ipAddress.getPort() : cluster.getDefCkport(); + channel.connect(new InetSocketAddress(ipAddress.getIp(), port)); + + SelectionKey key + = channel.register(selector, SelectionKey.OP_CONNECT | SelectionKey.OP_READ); + key.attach(beat); + keyMap.put(beat.toString(), new BeatKey(key)); + + beat.setStartTime(System.currentTimeMillis()); + + NIO_EXECUTOR.schedule(new TimeOutTask(key), + CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } catch (Exception e) { + beat.finishCheck(false, false, Switch.getTcpHealthParams().getMax(), "tcp:error:" + e.getMessage()); + + if (channel != null) { + try { + channel.close(); + } catch (Exception ignore) { + } + } + } + + return null; + } + } + + @Override + public String getType() { + return "TCP"; + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/misc/DomainStatusSynchronizer.java b/naming/src/main/java/com/alibaba/nacos/naming/misc/DomainStatusSynchronizer.java new file mode 100644 index 00000000000..6354372c923 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/misc/DomainStatusSynchronizer.java @@ -0,0 +1,98 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.misc; + +import com.alibaba.nacos.naming.boot.RunningConfig; +import com.ning.http.client.AsyncCompletionHandler; +import com.ning.http.client.Response; +import org.apache.commons.lang3.StringUtils; + +import java.net.HttpURLConnection; +import java.util.HashMap; +import java.util.Map; + +/** + * @author nacos + */ +public class DomainStatusSynchronizer implements Synchronizer { + @Override + public void send(final String serverIP, Message msg) { + if(serverIP == null) { + return; + } + + Map params = new HashMap(10); + + params.put("domsStatus", msg.getData()); + params.put("clientIP", NetUtils.localIP()); + + + String url = "http://" + serverIP + ":" + RunningConfig.getServerPort() + RunningConfig.getContextPath() + + UtilsAndCommons.NACOS_NAMING_CONTEXT + "/api/domStatus"; + + if (serverIP.contains(UtilsAndCommons.CLUSTER_CONF_IP_SPLITER)) { + url = "http://" + serverIP + RunningConfig.getContextPath() + + UtilsAndCommons.NACOS_NAMING_CONTEXT + "/api/domStatus"; + } + + try { + HttpClient.asyncHttpPost(url, null, params, new AsyncCompletionHandler() { + @Override + public Integer onCompleted(Response response) throws Exception { + if (response.getStatusCode() != HttpURLConnection.HTTP_OK) { + Loggers.SRV_LOG.warn("STATUS-SYNCHRONIZE", "failed to requerst domStatus, remote server: " + serverIP); + + return 1; + } + return 0; + } + }); + } catch (Exception e) { + Loggers.SRV_LOG.warn("STATUS-SYNCHRONIZE", "failed to requerst domStatus, remote server: " + serverIP, e); + } + + } + + @Override + public Message get(String serverIP, String key) { + if(serverIP == null) { + return null; + } + + Map params = new HashMap<>(10); + + params.put("dom", key); + + String result; + try { + Loggers.SRV_LOG.info("STATUS-SYNCHRONIZE", "sync dom status from: " + + serverIP + ", dom: " + key); + result = NamingProxy.reqAPI("ip4Dom2", params, serverIP, false); + } catch (Exception e) { + Loggers.SRV_LOG.warn("STATUS-SYNCHRONIZE","Failed to get domain status from " + serverIP, e); + return null; + } + + if(result == null || result.equals(StringUtils.EMPTY)) { + return null; + } + + Message msg = new Message(); + msg.setData(result); + + return msg; + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/misc/HttpClient.java b/naming/src/main/java/com/alibaba/nacos/naming/misc/HttpClient.java new file mode 100644 index 00000000000..4d9170168d8 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/misc/HttpClient.java @@ -0,0 +1,398 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.misc; + +import com.alibaba.nacos.common.util.IoUtils; +import com.ning.http.client.AsyncCompletionHandler; +import com.ning.http.client.AsyncHttpClient; +import com.ning.http.client.AsyncHttpClientConfig; +import com.ning.http.client.FluentStringsMap; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.*; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.message.BasicNameValuePair; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.zip.GZIPInputStream; + +/** + * @author nacos + */ +public class HttpClient { + public static final int TIME_OUT_MILLIS = 10000; + public static final int CON_TIME_OUT_MILLIS = 5000; + + private static AsyncHttpClient asyncHttpClient; + + static { + AsyncHttpClientConfig.Builder builder = new AsyncHttpClientConfig.Builder(); + builder.setMaximumConnectionsTotal(-1); + builder.setMaximumConnectionsPerHost(128); + builder.setAllowPoolingConnection(true); + builder.setFollowRedirects(false); + builder.setIdleConnectionTimeoutInMs(TIME_OUT_MILLIS); + builder.setConnectionTimeoutInMs(CON_TIME_OUT_MILLIS); + builder.setCompressionEnabled(true); + builder.setIOThreadMultiplier(1); + builder.setMaxRequestRetry(0); + builder.setUserAgent(UtilsAndCommons.SERVER_VERSION); + + asyncHttpClient = new AsyncHttpClient(builder.build()); + } + + public static HttpResult httpGet(String url, List headers, Map paramValues) { + return httpGetWithTimeOut(url, headers, paramValues, CON_TIME_OUT_MILLIS, TIME_OUT_MILLIS, "UTF-8"); + } + + public static HttpResult httpGetWithTimeOut(String url, List headers, Map paramValues, int connectTimeout, int readTimeout) { + return httpGetWithTimeOut(url, headers, paramValues, connectTimeout, readTimeout, "UTF-8"); + } + + public static HttpResult httpGetWithTimeOut(String url, List headers, Map paramValues, int connectTimeout, int readTimeout, String encoding) { + HttpURLConnection conn = null; + try { + String encodedContent = encodingParams(paramValues, encoding); + url += (null == encodedContent) ? "" : ("?" + encodedContent); + + conn = (HttpURLConnection) new URL(url).openConnection(); + conn.setConnectTimeout(connectTimeout); + conn.setReadTimeout(readTimeout); + conn.setRequestMethod("GET"); + + conn.addRequestProperty("Client-Version", UtilsAndCommons.SERVER_VERSION); + setHeaders(conn, headers, encoding); + conn.connect(); + + return getResult(conn); + } catch (Exception e) { + Loggers.SRV_LOG.warn("VIPSRV", "Exception while request: " + url + ", caused: " + e.getMessage()); + return new HttpResult(500, e.toString(), Collections.emptyMap()); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + } + + public static HttpResult httpGet(String url, List headers, Map paramValues, String encoding) { + + HttpURLConnection conn = null; + try { + String encodedContent = encodingParams(paramValues, encoding); + url += (null == encodedContent) ? "" : ("?" + encodedContent); + + conn = (HttpURLConnection) new URL(url).openConnection(); + conn.setConnectTimeout(CON_TIME_OUT_MILLIS); + conn.setReadTimeout(TIME_OUT_MILLIS); + conn.setRequestMethod("GET"); + setHeaders(conn, headers, encoding); + conn.connect(); + + return getResult(conn); + } catch (Exception e) { + return new HttpResult(500, e.toString(), Collections.emptyMap()); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + } + + public static void asyncHttpGet(String url, List headers, Map paramValues, AsyncCompletionHandler handler) throws Exception { + if (!MapUtils.isEmpty(paramValues)) { + String encodedContent = encodingParams(paramValues, "UTF-8"); + url += (null == encodedContent) ? "" : ("?" + encodedContent); + } + + AsyncHttpClient.BoundRequestBuilder builder = asyncHttpClient.prepareGet(url); + + if (!CollectionUtils.isEmpty(headers)) { + for (String header : headers) { + builder.setHeader(header.split("=")[0], header.split("=")[1]); + } + } + + builder.setHeader("Accept-Charset", "UTF-8"); + + if (handler != null) { + builder.execute(handler); + } else { + builder.execute(); + } + } + + public static void asyncHttpPostLarge(String url, List headers, String content, AsyncCompletionHandler handler) throws Exception { + AsyncHttpClient.BoundRequestBuilder builder = asyncHttpClient.preparePost(url); + + if (!CollectionUtils.isEmpty(headers)) { + for (String header : headers) { + builder.setHeader(header.split("=")[0], header.split("=")[1]); + } + } + + builder.setBody(content.getBytes("UTF-8")); + + builder.setHeader("Content-Type", "application/json; charset=UTF-8"); + builder.setHeader("Accept-Charset", "UTF-8"); + builder.setHeader("Accept-Encoding", "gzip"); + builder.setHeader("Content-Encoding", "gzip"); + + if (handler != null) { + builder.execute(handler); + } else { + builder.execute(); + } + } + + public static void asyncHttpPostLarge(String url, List headers, byte[] content, AsyncCompletionHandler handler) throws Exception { + AsyncHttpClient.BoundRequestBuilder builder = asyncHttpClient.preparePost(url); + + if (!CollectionUtils.isEmpty(headers)) { + for (String header : headers) { + builder.setHeader(header.split("=")[0], header.split("=")[1]); + } + } + + builder.setBody(content); + + builder.setHeader("Content-Type", "application/json; charset=UTF-8"); + builder.setHeader("Accept-Charset", "UTF-8"); + builder.setHeader("Accept-Encoding", "gzip"); + builder.setHeader("Content-Encoding", "gzip"); + + if (handler != null) { + builder.execute(handler); + } else { + builder.execute(); + } + } + + public static void asyncHttpPost(String url, List headers, Map paramValues, AsyncCompletionHandler handler) throws Exception { + AsyncHttpClient.BoundRequestBuilder builder = asyncHttpClient.preparePost(url); + + if (!CollectionUtils.isEmpty(headers)) { + for (String header : headers) { + builder.setHeader(header.split("=")[0], header.split("=")[1]); + } + } + + if (!MapUtils.isEmpty(paramValues)) { + FluentStringsMap params = new FluentStringsMap(); + for (Map.Entry entry : paramValues.entrySet()) { + params.put(entry.getKey(), Collections.singletonList(entry.getValue())); + } + + builder.setParameters(params); + } + + builder.setHeader("Accept-Charset", "UTF-8"); + + if (handler != null) { + builder.execute(handler); + } else { + builder.execute(); + } + } + + public static HttpResult httpPost(String url, List headers, Map paramValues) { + return httpPost(url, headers, paramValues, "UTF-8"); + } + + public static HttpResult httpPost(String url, List headers, Map paramValues, String encoding) { + try { + HttpClientBuilder builder = HttpClients.custom(); + builder.setUserAgent(UtilsAndCommons.SERVER_VERSION); + builder.setConnectionTimeToLive(CON_TIME_OUT_MILLIS, TimeUnit.MILLISECONDS); + + CloseableHttpClient httpClient = builder.build(); + HttpPost httpost = new HttpPost(url); + + RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(5000).setConnectTimeout(5000).setSocketTimeout(5000).setRedirectsEnabled(true).setMaxRedirects(5).build(); + httpost.setConfig(requestConfig); + + List nvps = new ArrayList(); + + for (Map.Entry entry : paramValues.entrySet()) { + nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue())); + } + + + httpost.setEntity(new UrlEncodedFormEntity(nvps, encoding)); + HttpResponse response = httpClient.execute(httpost); + HttpEntity entity = response.getEntity(); + + String charset = encoding; + if (entity.getContentType() != null) { + + HeaderElement[] headerElements = entity.getContentType().getElements(); + + if (headerElements != null && headerElements.length > 0 && headerElements[0] != null && + headerElements[0].getParameterByName("charset") != null) { + charset = headerElements[0].getParameterByName("charset").getValue(); + } + } + + return new HttpResult(response.getStatusLine().getStatusCode(), IoUtils.toString(entity.getContent(), charset), Collections.emptyMap()); + } catch (Throwable e) { + return new HttpResult(500, e.toString(), Collections.emptyMap()); + } + } + + public static HttpResult httpPostLarge(String url, Map headers, String content) { + try { + HttpClientBuilder builder = HttpClients.custom(); + builder.setUserAgent(UtilsAndCommons.SERVER_VERSION); + builder.setConnectionTimeToLive(500, TimeUnit.MILLISECONDS); + + CloseableHttpClient httpClient = builder.build(); + HttpPost httpost = new HttpPost(url); + + for (Map.Entry entry : headers.entrySet()) { + httpost.setHeader(entry.getKey(), entry.getValue()); + } + + httpost.setEntity(new StringEntity(content, ContentType.create("application/json", "UTF-8"))); + HttpResponse response = httpClient.execute(httpost); + HttpEntity entity = response.getEntity(); + + HeaderElement[] headerElements = entity.getContentType().getElements(); + String charset = headerElements[0].getParameterByName("charset").getValue(); + + return new HttpResult(response.getStatusLine().getStatusCode(), + IoUtils.toString(entity.getContent(), charset), Collections.emptyMap()); + } catch (Exception e) { + return new HttpResult(500, e.toString(), Collections.emptyMap()); + } + } + + private static HttpResult getResult(HttpURLConnection conn) throws IOException { + int respCode = conn.getResponseCode(); + + InputStream inputStream; + if (HttpURLConnection.HTTP_OK == respCode) { + inputStream = conn.getInputStream(); + } else { + inputStream = conn.getErrorStream(); + } + + Map respHeaders = new HashMap(conn.getHeaderFields().size()); + for (Map.Entry> entry : conn.getHeaderFields().entrySet()) { + respHeaders.put(entry.getKey(), entry.getValue().get(0)); + } + + String gzipEncoding = "gzip"; + + if (gzipEncoding.equals(respHeaders.get(HttpHeaders.CONTENT_ENCODING))) { + inputStream = new GZIPInputStream(inputStream); + } + + HttpResult result = new HttpResult(respCode, IoUtils.toString(inputStream, getCharset(conn)), respHeaders); + inputStream.close(); + + return result; + } + + private static String getCharset(HttpURLConnection conn) { + String contentType = conn.getContentType(); + if (StringUtils.isEmpty(contentType)) { + return "utf-8"; + } + + String[] values = contentType.split(";"); + if (values.length == 0) { + return "utf-8"; + } + + String charset = "utf-8"; + for (String value : values) { + value = value.trim(); + + if (value.toLowerCase().startsWith("charset=")) { + charset = value.substring("charset=".length()); + } + } + + return charset; + } + + private static void setHeaders(HttpURLConnection conn, List headers, String encoding) { + if (null != headers) { + for (Iterator iter = headers.iterator(); iter.hasNext(); ) { + conn.addRequestProperty(iter.next(), iter.next()); + } + } + + conn.addRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=" + + encoding); + conn.addRequestProperty("Accept-Charset", encoding); + conn.addRequestProperty("Client-Version", UtilsAndCommons.SERVER_VERSION); + } + + public static String encodingParams(Map params, String encoding) + throws UnsupportedEncodingException { + StringBuilder sb = new StringBuilder(); + if (null == params || params.isEmpty()) { + return null; + } + + params.put("encoding", encoding); + params.put("nofix", "1"); + + for (Map.Entry entry : params.entrySet()) { + if (StringUtils.isEmpty(entry.getValue())) { + continue; + } + + sb.append(entry.getKey()).append("="); + sb.append(URLEncoder.encode(entry.getValue(), encoding)); + sb.append("&"); + } + + return sb.toString(); + } + + public static class HttpResult { + final public int code; + final public String content; + final private Map respHeaders; + + public HttpResult(int code, String content, Map respHeaders) { + this.code = code; + this.content = content; + this.respHeaders = respHeaders; + } + + public String getHeader(String name) { + return respHeaders.get(name); + } + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/misc/Loggers.java b/naming/src/main/java/com/alibaba/nacos/naming/misc/Loggers.java new file mode 100644 index 00000000000..a1c47684655 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/misc/Loggers.java @@ -0,0 +1,68 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.misc; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.joran.JoranConfigurator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; + +/** + * @author nacos + */ +public class Loggers { + + static { + + LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); + lc.reset(); + JoranConfigurator configurator = new JoranConfigurator(); + configurator.setContext(lc); + + try { + configurator.doConfigure(System.getProperty("nacos.home") + "/conf/nacos-logback.xml"); + } catch (Exception ignore) { + } + + try { + configurator.doConfigure(Loggers.class + .getResource("/naming-logback.xml")); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Init logger failed!", e); + } + } + + public static final Logger PUSH = LoggerFactory.getLogger("com.alibaba.nacos.naming.push"); + + public static final Logger CHECK_RT = LoggerFactory.getLogger("com.alibaba.nacos.naming.rt"); + + public static final Logger SRV_LOG = LoggerFactory.getLogger("com.alibaba.nacos.naming.main"); + + public static final Logger EVT_LOG = LoggerFactory.getLogger("com.alibaba.nacos.naming.event"); + + public static final Logger RAFT = LoggerFactory.getLogger("com.alibaba.nacos.naming.raft"); + + public static final Logger PERFORMANCE_LOG = LoggerFactory.getLogger("com.alibaba.nacos.naming.performance"); + + public static final Logger ROLE_LOG = LoggerFactory.getLogger("com.alibaba.nacos.naming.router"); + + public static final Logger DEBUG_LOG = LoggerFactory.getLogger("com.alibaba.nacos.naming.debug"); + + public static final Logger TENANT = LoggerFactory.getLogger("com.alibaba.nacos.naming.tenant"); +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/misc/Message.java b/naming/src/main/java/com/alibaba/nacos/naming/misc/Message.java new file mode 100644 index 00000000000..0f0f0931b25 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/misc/Message.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.misc; + +/** + * @author nacos + */ +public class Message { + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + private String data; +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/misc/NamingProxy.java b/naming/src/main/java/com/alibaba/nacos/naming/misc/NamingProxy.java new file mode 100644 index 00000000000..ac1ed7ca789 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/misc/NamingProxy.java @@ -0,0 +1,264 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.misc; + +import com.alibaba.nacos.common.util.IoUtils; +import com.alibaba.nacos.common.util.SystemUtil; +import com.alibaba.nacos.naming.boot.RunningConfig; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +/** + * @author nacos + */ +public class NamingProxy { + + private static volatile List servers; + + private static List serverlistFromConfig; + + private static List lastServers = new ArrayList(); + + private static Map> serverListMap = new ConcurrentHashMap>(); + + private static long lastSrvRefTime = 0L; + + /** + * records last time that query site info of servers and localhost from armory + */ + private static long lastSrvSiteRefreshTime = 0L; + + private static long VIP_SRV_REF_INTER_MILLIS = TimeUnit.SECONDS.toMillis(30); + + /** + * query site info of servers and localhost every 12 hours + */ + private static final long VIP_SRV_SITE_REF_INTER_MILLIS = TimeUnit.HOURS.toMillis(1); + + private static String jmenv; + + public static String getJmenv() { + jmenv = SystemUtil.getSystemEnv("nacos_jmenv_domain"); + + if (StringUtils.isEmpty(jmenv)) { + jmenv = System.getProperty("com.alibaba.nacos.naming.jmenv", "jmenv.tbsite.net"); + } + + if (StringUtils.isEmpty(jmenv)) { + jmenv = "jmenv.tbsite.net"; + } + + return jmenv; + } + + private static void refreshSrvSiteIfNeed() { + refreshSrvIfNeed(); + try { + if (System.currentTimeMillis() - lastSrvSiteRefreshTime > VIP_SRV_SITE_REF_INTER_MILLIS || + !CollectionUtils.isEqualCollection(servers, lastServers)) { + if (!CollectionUtils.isEqualCollection(servers, lastServers)) { + Loggers.SRV_LOG.info("REFRESH-SERVER-SITE", "server list is changed, old: " + lastServers + ", new: " + servers); + } + + lastServers = servers; + } + } catch (Exception e) { + Loggers.SRV_LOG.warn("fail to query server site: ", e); + } + } + + public static List getServers() { + refreshSrvIfNeed(); + return servers; + } + + public static void refreshSrvIfNeed() { + refreshSrvIfNeed(StringUtils.EMPTY); + } + + public static void refreshSrvIfNeed(String env) { + try { + if (System.currentTimeMillis() - lastSrvRefTime < VIP_SRV_REF_INTER_MILLIS) { + return; + } + + if (UtilsAndCommons.STANDALONE_MODE) { + servers = new ArrayList<>(); + servers.add(InetAddress.getLocalHost().getHostAddress() + ":" + RunningConfig.getServerPort()); + return; + } + + List serverlist = refreshServerListFromDisk(); + + List list = new ArrayList(); + if (!CollectionUtils.isEmpty(serverlist)) { + serverlistFromConfig = serverlist; + if (list.isEmpty()) { + Loggers.SRV_LOG.warn("Can not acquire server list"); + } + } + + + if (!StringUtils.isEmpty(env)) { + serverListMap.put(env, list); + } else { + if (!CollectionUtils.isEqualCollection(serverlistFromConfig, list) && CollectionUtils.isNotEmpty(serverlistFromConfig)) { + Loggers.SRV_LOG.info("SERVER-LIST", "server list is not the same between AS and config file, use config file."); + servers = serverlistFromConfig; + } else { + servers = list; + } + } + + if (RunningConfig.getServerPort() > 0) { + lastSrvRefTime = System.currentTimeMillis(); + } + } catch (Exception e) { + Loggers.SRV_LOG.warn("failed to update server list", e); + List serverlist = refreshServerListFromDisk(); + + if (CollectionUtils.isNotEmpty(serverlist)) { + serverlistFromConfig = serverlist; + } + + if (CollectionUtils.isNotEmpty(serverlistFromConfig)) { + servers = serverlistFromConfig; + } + } + } + + public static List refreshServerListFromDisk() { + + List result = new ArrayList<>(); + // read nacos config if necessary. + try { + result = IoUtils.readLines(new InputStreamReader(new FileInputStream(UtilsAndCommons.getConfFile()), "UTF-8")); + } catch (Exception e) { + Loggers.SRV_LOG.warn("failed to get config: " + UtilsAndCommons.getConfFile(), e); + } + + Loggers.DEBUG_LOG.debug("REFRESH-SERVER-LIST1", result); + + //use system env + if (CollectionUtils.isEmpty(result)) { + result = SystemUtil.getIPsBySystemEnv(UtilsAndCommons.SELF_SERVICE_CLUSTER_ENV); + Loggers.DEBUG_LOG.debug("REFRESH-SERVER-LIST4: " + result); + } + + Loggers.DEBUG_LOG.debug("REFRESH-SERVER-LIST2" + result); + + if (!result.isEmpty() && !result.get(0).contains(UtilsAndCommons.CLUSTER_CONF_IP_SPLITER)) { + for (int i = 0; i < result.size(); i++) { + result.set(i, result.get(i) + UtilsAndCommons.CLUSTER_CONF_IP_SPLITER + RunningConfig.getServerPort()); + } + } + + return result; + } + + /** + * This method will classify all servers as two kinds of servers: servers in the same site with local host and others + * + * @return servers + */ + public static ConcurrentHashMap> getSameSiteServers() { + refreshSrvSiteIfNeed(); + List snapshot = servers; + ConcurrentHashMap> servers = new ConcurrentHashMap<>(2); + servers.put("sameSite", snapshot); + servers.put("otherSite", new ArrayList()); + + Loggers.SRV_LOG.debug("sameSiteServers:" + servers.toString()); + return servers; + } + + public static String reqAPI(String api, Map params, String curServer, boolean isPost) throws Exception { + try { + List headers = Arrays.asList("Client-Version", UtilsAndCommons.SERVER_VERSION, + "Accept-Encoding", "gzip,deflate,sdch", + "Connection", "Keep-Alive", + "Content-Encoding", "gzip"); + + + HttpClient.HttpResult result; + + if (!curServer.contains(UtilsAndCommons.CLUSTER_CONF_IP_SPLITER)) { + curServer = curServer + UtilsAndCommons.CLUSTER_CONF_IP_SPLITER + RunningConfig.getServerPort(); + } + + if (isPost) { + result = HttpClient.httpPost("http://" + curServer + RunningConfig.getContextPath() + + UtilsAndCommons.NACOS_NAMING_CONTEXT + "/api/" + api, headers, params); + } else { + result = HttpClient.httpGet("http://" + curServer + RunningConfig.getContextPath() + + UtilsAndCommons.NACOS_NAMING_CONTEXT + "/api/" + api, headers, params); + } + + if (HttpURLConnection.HTTP_OK == result.code) { + return result.content; + } + + if (HttpURLConnection.HTTP_NOT_MODIFIED == result.code) { + return StringUtils.EMPTY; + } + + throw new IOException("failed to req API:" + "http://" + curServer + + RunningConfig.getContextPath() + + UtilsAndCommons.NACOS_NAMING_CONTEXT + "/api/" + api + ". code:" + + result.code + " msg: " + result.content); + } catch (Exception e) { + Loggers.SRV_LOG.warn("NamingProxy", e); + } + return StringUtils.EMPTY; + } + + public static String getEnv() { + try { + + String urlString = "http://" + getJmenv() + ":8080" + "/env"; + + List headers = Arrays.asList("Client-Version", UtilsAndCommons.SERVER_VERSION, + "Accept-Encoding", "gzip,deflate,sdch", + "Connection", "Keep-Alive"); + + HttpClient.HttpResult result = HttpClient.httpGet(urlString, headers, null); + if (HttpURLConnection.HTTP_OK != result.code) { + throw new IOException("Error while requesting: " + urlString + "'. Server returned: " + + result.code); + } + + String content = result.content; + + return content.trim(); + } catch (Exception e) { + Loggers.SRV_LOG.warn("failed to get env", e); + } + + return "sh"; + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/misc/NetUtils.java b/naming/src/main/java/com/alibaba/nacos/naming/misc/NetUtils.java new file mode 100644 index 00000000000..af812a4decf --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/misc/NetUtils.java @@ -0,0 +1,48 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.misc; + +import com.alibaba.nacos.naming.boot.RunningConfig; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * @author nacos + */ +public class NetUtils { + + public static String localIP() { + try { + return InetAddress.getLocalHost().getHostAddress() + ":" + RunningConfig.getServerPort(); + } catch (UnknownHostException e) { + return "resolve_failed"; + } + } + + public static String num2ip(int ip) { + int[] b = new int[4]; + String x = ""; + + b[0] = (int) ((ip >> 24) & 0xff); + b[1] = (int) ((ip >> 16) & 0xff); + b[2] = (int) ((ip >> 8) & 0xff); + b[3] = (int) (ip & 0xff); + x = Integer.toString(b[0]) + "." + Integer.toString(b[1]) + "." + Integer.toString(b[2]) + "." + Integer.toString(b[3]); + + return x; + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/misc/ServerStatusSynchronizer.java b/naming/src/main/java/com/alibaba/nacos/naming/misc/ServerStatusSynchronizer.java new file mode 100644 index 00000000000..8e23d4aa8e3 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/misc/ServerStatusSynchronizer.java @@ -0,0 +1,71 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.misc; + +import com.alibaba.nacos.naming.boot.RunningConfig; +import com.ning.http.client.AsyncCompletionHandler; +import com.ning.http.client.Response; + +import java.net.HttpURLConnection; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author nacos + */ +public class ServerStatusSynchronizer implements Synchronizer { + @Override + public void send(final String serverIP, Message msg) { + if(serverIP == null) { + return; + } + + final Map params = new HashMap(2); + + params.put("serverStatus", msg.getData()); + + String url = "http://" + serverIP + ":" + RunningConfig.getServerPort() + + RunningConfig.getContextPath() + UtilsAndCommons.NACOS_NAMING_CONTEXT + "/api/serverStatus"; + + if (serverIP.contains(UtilsAndCommons.CLUSTER_CONF_IP_SPLITER)) { + url = "http://" + serverIP + RunningConfig.getContextPath() + UtilsAndCommons.NACOS_NAMING_CONTEXT + + "/api/serverStatus"; + } + + try { + HttpClient.asyncHttpGet(url, null, params, new AsyncCompletionHandler() { + @Override + public Integer onCompleted(Response response) throws Exception { + if (response.getStatusCode() != HttpURLConnection.HTTP_OK) { + Loggers.SRV_LOG.warn("STATUS-SYNCHRONIZE", "failed to requerst serverStatus, remote server: " + serverIP); + + return 1; + } + return 0; + } + }); + } catch (Exception e) { + Loggers.SRV_LOG.warn("STATUS-SYNCHRONIZE", "failed to requerst serverStatus, remote server: " + serverIP, e); + } + } + + @Override + public Message get(String server, String key) { + return null; + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/misc/Switch.java b/naming/src/main/java/com/alibaba/nacos/naming/misc/Switch.java new file mode 100644 index 00000000000..8b09dc7b02d --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/misc/Switch.java @@ -0,0 +1,349 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.misc; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.TypeReference; +import com.alibaba.nacos.naming.raft.RaftCore; +import com.alibaba.nacos.naming.raft.RaftListener; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author nacos + */ +public class Switch { + private static volatile SwitchDomain dom = new SwitchDomain(); + private static boolean enableService = false; + + public static long getClientBeatInterval() { + return dom.getClientBeatInterval(); + } + + public static void setClientBeatInterval(long clientBeatInterval) { + dom.setClientBeatInterval(clientBeatInterval); + } + + + static { + + Loggers.RAFT.info("Switch init start!"); + + RaftCore.listen(new RaftListener() { + @Override + public boolean interests(String key) { + return StringUtils.equals(key, UtilsAndCommons.DOMAINS_DATA_ID + ".00-00---000-VIPSRV_SWITCH_DOMAIN-000---00-00"); + } + + @Override + public boolean matchUnlistenKey(String key) { + return StringUtils.equals(key, UtilsAndCommons.DOMAINS_DATA_ID + ".00-00---000-VIPSRV_SWITCH_DOMAIN-000---00-00"); + } + + @Override + public void onChange(String key, String value) throws Exception { + Loggers.RAFT.info("VIPSRV-RAFT", "datum is changed, key: " + key + ", value: " + value); + if (StringUtils.isEmpty(value)) { + return; + } + SwitchDomain switchDomain = JSON.parseObject(value, new TypeReference() { + }); + + dom = switchDomain; + } + + @Override + public void onDelete(String key, String value) throws Exception { + + } + }); + } + + public static long getPushCacheMillis(String dom) { + if (Switch.dom.pushCacheMillisMap == null + || !Switch.dom.pushCacheMillisMap.containsKey(dom)) { + return Switch.dom.defaultPushCacheMillis; + } + + return Switch.dom.pushCacheMillisMap.get(dom); + } + + public static long getPushCacheMillis() { + return Switch.dom.defaultPushCacheMillis; + } + + public static long getCacheMillis(String dom) { + if (Switch.dom.cacheMillisMap == null + || !Switch.dom.cacheMillisMap.containsKey(dom)) { + return Switch.dom.defaultCacheMillis; + } + + return Switch.dom.cacheMillisMap.get(dom); + } + + public static long getCacheMillis() { + return Switch.dom.defaultCacheMillis; + } + + public static void setPushCacheMillis(String dom, Long cacheMillis) { + if (StringUtils.isEmpty(dom)) { + Switch.dom.defaultPushCacheMillis = cacheMillis; + } else { + Switch.dom.pushCacheMillisMap.put(dom, cacheMillis); + } + } + + public static void setCacheMillis(String dom, long cacheMillis) { + if (StringUtils.isEmpty(dom)) { + Switch.dom.defaultCacheMillis = cacheMillis; + } else { + Switch.dom.cacheMillisMap.put(dom, cacheMillis); + } + } + + public static SwitchDomain getDom() { + return dom; + } + + public static List getMasters() { + return dom.masters; + } + + public static void setMasters(List masters) { + dom.masters = masters; + } + + public static void setDom(SwitchDomain dom) { + Switch.dom = dom; + } + + public static void save() { + try { + RaftCore.signalPublish(UtilsAndCommons.getDomStoreKey(dom), JSON.toJSONString(dom)); + } catch (Exception e) { + Loggers.SRV_LOG.error("VIPSRV-SWITCH", "failed to save switch", e); + } + } + + public static SwitchDomain.HttpHealthParams getHttpHealthParams() { + return dom.httpHealthParams; + } + + public static SwitchDomain.MysqlHealthParams getMysqlHealthParams() { + return dom.mysqlHealthParams; + } + + public static SwitchDomain.TcpHealthParams getTcpHealthParams() { + return dom.tcpHealthParams; + } + + public static boolean isHealthCheckEnabled() { + return Switch.dom.healthCheckEnabled; + } + + public static boolean isHealthCheckEnabled(String dom) { + return Switch.dom.healthCheckEnabled || Switch.dom.getHealthCheckWhiteList().contains(dom); + } + + public static void setHeathCheckEnabled(boolean enabled) { + Switch.dom.healthCheckEnabled = enabled; + } + + public static boolean isEnableAuthentication() { + return dom.isEnableAuthentication(); + } + + public static void setEnableAuthentication(boolean enableAuthentication) { + dom.setEnableAuthentication(enableAuthentication); + } + + public static boolean isDistroEnabled() { + return Switch.dom.distroEnabled; + } + + public static void setDistroEnabled(boolean enabled) { + Switch.dom.distroEnabled = enabled; + } + + public static void setDistroThreshold(float distroThreshold) { + dom.distroThreshold = distroThreshold; + } + + public static float getDistroThreshold() { + return dom.distroThreshold; + } + + public static Integer getAdWeight(String ip) { + if (dom.adWeightMap == null + || !dom.adWeightMap.containsKey(ip)) { + return 0; + } + + return dom.adWeightMap.get(ip); + } + + public static void setAdWeight(String ip, int weight) { + dom.adWeightMap.put(ip, weight); + } + + public static String getPushJavaVersion() { + return dom.pushJavaVersion; + } + + public static String getPushPythonVersion() { + return dom.pushPythonVersion; + } + + public static String getPushCVersion() { + return dom.pushCVersion; + } + + public static void setPushJavaVersion(String pushJavaVersion) { + dom.pushJavaVersion = pushJavaVersion; + } + + public static void setPushPythonVersion(String pushPythonVersion) { + dom.pushPythonVersion = pushPythonVersion; + } + + public static void setPushCVersion(String pushCVersion) { + dom.pushCVersion = pushCVersion; + } + + public static int getCheckTimes() { + return dom.checkTimes; + } + + public static void setCheckTimes(int times) { + dom.checkTimes = times; + } + + public static long getdistroServerExpiredMillis() { + return dom.distroServerExpiredMillis; + } + + public static long getServerStatusSynchronizationPeriodMillis() { + return dom.serverStatusSynchronizationPeriodMillis; + } + + public static void setServerStatusSynchronizationPeriodMillis(long serverStatusSynchronizationPeriodMillis) { + dom.serverStatusSynchronizationPeriodMillis = serverStatusSynchronizationPeriodMillis; + } + + public static long getDomStatusSynchronizationPeriodMillis() { + return dom.domStatusSynchronizationPeriodMillis; + } + + public static void setDomStatusSynchronizationPeriodMillis(long domStatusSynchronizationPeriodMillis) { + dom.domStatusSynchronizationPeriodMillis = domStatusSynchronizationPeriodMillis; + } + + public static boolean getDisableAddIP() { + return dom.disableAddIP; + } + + public static void setDisableAddIP(boolean enable) { + dom.disableAddIP = enable; + } + + public static boolean getEnableCache() { + return dom.enableCache; + } + + public static void setEnableCache(boolean enableCache) { + dom.enableCache = enableCache; + } + + public static Map getLimitedUrlMap() { + return dom.limitedUrlMap; + } + + public static void setLimitedUrlMap(Map limitedUrlMap) { + dom.limitedUrlMap = limitedUrlMap; + } + + public static void setTrafficSchedulingJavaVersion(String version) { + dom.trafficSchedulingJavaVersion = version; + } + + public static String getTrafficSchedulingJavaVersion() { + return dom.trafficSchedulingJavaVersion; + } + + public static void setTrafficSchedulingPythonVersion(String version) { + dom.trafficSchedulingPythonVersion = version; + } + + public static String getTrafficSchedulingPythonVersion() { + return dom.trafficSchedulingPythonVersion; + } + + public static void setTrafficSchedulingCVersion(String version) { + dom.trafficSchedulingCVersion = version; + } + + public static String getTrafficSchedulingCVersion() { + return dom.trafficSchedulingCVersion; + } + + public static void setTrafficSchedulingTengineVersion(String version) { + dom.trafficSchedulingTengineVersion = version; + } + + public static String getTrafficSchedulingTengineVersion() { + return dom.trafficSchedulingTengineVersion; + } + + + public static boolean isSendBeatOnly() { + return dom.isSendBeatOnly(); + } + + public static void setSendBeatOnly(boolean sentBeatOnly) { + dom.setSendBeatOnly(sentBeatOnly); + } + + public static boolean isEnableStandalone() { + return dom.isEnableStandalone(); + } + + public static void setEnableStandalone(boolean enableStandalone) { + dom.setEnableStandalone(enableStandalone); + } + + public static Set getHealthCheckWhiteList() { + return dom.getHealthCheckWhiteList(); + } + + public static void setHealthCheckWhiteList(Set healthCheckWhiteList) { + dom.setHealthCheckWhiteList(healthCheckWhiteList); + } + + public static List getIncrementalList() { + return dom.getIncrementalList(); + } + + public static boolean isAllDomNameCache() { + return dom.isAllDomNameCache(); + } + + public static void setAllDomNameCache(boolean enable) { + dom.setAllDomNameCache(enable); + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/misc/SwitchDomain.java b/naming/src/main/java/com/alibaba/nacos/naming/misc/SwitchDomain.java new file mode 100644 index 00000000000..7c52ac665ce --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/misc/SwitchDomain.java @@ -0,0 +1,391 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.misc; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.annotation.JSONField; +import com.alibaba.nacos.naming.core.Domain; +import com.alibaba.nacos.naming.core.IpAddress; +import com.alibaba.nacos.naming.raft.RaftListener; +import org.apache.commons.lang3.StringUtils; +import sun.reflect.generics.reflectiveObjects.NotImplementedException; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +/** + * @author nacos + */ +public class SwitchDomain implements Domain, RaftListener { + public String name = "00-00---000-VIPSRV_SWITCH_DOMAIN-000---00-00"; + + public List masters; + + public Map adWeightMap = new HashMap(); + + public long defaultPushCacheMillis = TimeUnit.SECONDS.toMillis(10); + + private long clientBeatInterval = 5 * 1000; + + public long defaultCacheMillis = 1000L; + + public float distroThreshold = 0.7F; + + public String token = UtilsAndCommons.SUPER_TOKEN; + + public Map cacheMillisMap = new HashMap(); + + public Map pushCacheMillisMap = new HashMap(); + + public boolean healthCheckEnabled = true; + + public boolean distroEnabled = true; + + public boolean enableStandalone = true; + + public int checkTimes = 3; + + public HttpHealthParams httpHealthParams = new HttpHealthParams(); + + public TcpHealthParams tcpHealthParams = new TcpHealthParams(); + + public MysqlHealthParams mysqlHealthParams = new MysqlHealthParams(); + + private List incrementalList = new ArrayList<>(); + + private boolean allDomNameCache = true; + + public long serverStatusSynchronizationPeriodMillis = TimeUnit.SECONDS.toMillis(15); + + public long domStatusSynchronizationPeriodMillis = TimeUnit.SECONDS.toMillis(5); + + public boolean disableAddIP = false; + + public boolean enableCache = true; + + public boolean sendBeatOnly = false; + + public Map limitedUrlMap = new HashMap<>(); + + /** + * The server is regarded as expired if its two reporting interval is lagger than this variable. + */ + public long distroServerExpiredMillis = 30000; + + /** + * since which version, push can be enabled + */ + public String pushJavaVersion = "4.1.0"; + public String pushPythonVersion = "0.4.3"; + public String pushCVersion = "1.0.12"; + public String trafficSchedulingJavaVersion = "4.5.0"; + public String trafficSchedulingPythonVersion = "9999.0.0"; + public String trafficSchedulingCVersion = "1.0.5"; + public String trafficSchedulingTengineVersion = "2.0.0"; + + public boolean enableAuthentication = false; + + public boolean isEnableAuthentication() { + return enableAuthentication; + } + + public void setEnableAuthentication(boolean enableAuthentication) { + this.enableAuthentication = enableAuthentication; + } + + public Set getHealthCheckWhiteList() { + return healthCheckWhiteList; + } + + public void setHealthCheckWhiteList(Set healthCheckWhiteList) { + this.healthCheckWhiteList = healthCheckWhiteList; + } + + private Set healthCheckWhiteList = new HashSet<>(); + + public long getClientBeatInterval() { + return clientBeatInterval; + } + + public void setClientBeatInterval(long clientBeatInterval) { + this.clientBeatInterval = clientBeatInterval; + } + + public boolean isEnableCache() { + return enableCache; + } + + public boolean isEnableStandalone() { + return enableStandalone; + } + + public void setEnableStandalone(boolean enableStandalone) { + this.enableStandalone = enableStandalone; + } + + public SwitchDomain() { + } + + @Override + public String getToken() { + return token; + } + + @Override + public void setToken(String token) { + this.token = token; + } + + @Override + public List getOwners() { + return masters; + } + + public boolean isSendBeatOnly() { + return sendBeatOnly; + } + + public void setSendBeatOnly(boolean sendBeatOnly) { + this.sendBeatOnly = sendBeatOnly; + } + + @Override + public void setOwners(List owners) { + this.masters = owners; + } + + // the followings are not implemented + + @Override + public String getName() { + return "00-00---000-VIPSRV_SWITCH_DOMAIN-000---00-00"; + } + + @Override + public void setName(String name) { + + } + + @Override + public void init() { + + } + + @Override + public void destroy() throws Exception { + + } + + @Override + public List allIPs() { + return null; + } + + @Override + public List srvIPs(String clientIp) { + return null; + } + + public String toJSON() { + return JSON.toJSONString(this); + } + + @Override + public void setProtectThreshold(float protectThreshold) { + + } + + @Override + public float getProtectThreshold() { + return 0; + } + + @Override + public void update(Domain dom) { + + } + + @Override + @JSONField(serialize = false) + public String getChecksum() { + throw new NotImplementedException(); + } + + @Override + public void recalculateChecksum() { + throw new NotImplementedException(); + } + + + @Override + public boolean interests(String key) { + return StringUtils.equals(key, UtilsAndCommons.DOMAINS_DATA_ID + "." + name); + } + + @Override + public boolean matchUnlistenKey(String key) { + return StringUtils.equals(key, UtilsAndCommons.DOMAINS_DATA_ID + "." + name); + } + + @Override + public void onChange(String key, String value) throws Exception { + SwitchDomain domain = JSON.parseObject(value, SwitchDomain.class); + update(domain); + } + + @Override + public void onDelete(String key, String value) throws Exception { + + } + + public List getIncrementalList() { + return incrementalList; + } + + public boolean isAllDomNameCache() { + return allDomNameCache; + } + + public void setAllDomNameCache(boolean enable) { + allDomNameCache = enable; + } + + public interface HealthParams { + /** + * Maximum RT + * + * @return Max RT + */ + int getMax(); + + /** + * Minimum RT + * + * @return Minimum RT + */ + int getMin(); + + /** + * Get Factor to reevaluate RT + * + * @return reevaluate factor + */ + float getFactor(); + } + + public static class HttpHealthParams implements HealthParams { + + public static final int MIN_MAX = 3000; + public static final int MIN_MIN = 500; + + private int max = 5000; + private int min = 500; + private float factor = 0.85F; + + @Override + public int getMax() { + return max; + } + + @Override + public int getMin() { + return min; + } + + @Override + public float getFactor() { + return factor; + } + + public void setFactor(float factor) { + this.factor = factor; + } + + public void setMax(int max) { + this.max = max; + } + + public void setMin(int min) { + this.min = min; + } + } + + public static class MysqlHealthParams implements HealthParams { + private int max = 3000; + private int min = 2000; + private float factor = 0.65F; + + @Override + public int getMax() { + return max; + } + + @Override + public int getMin() { + return min; + } + + @Override + public float getFactor() { + return factor; + } + + public void setFactor(float factor) { + this.factor = factor; + } + + public void setMax(int max) { + this.max = max; + } + + public void setMin(int min) { + this.min = min; + } + } + + public static class TcpHealthParams implements HealthParams { + private int max = 5000; + private int min = 1000; + private float factor = 0.75F; + + @Override + public int getMax() { + return max; + } + + @Override + public int getMin() { + return min; + } + + @Override + public float getFactor() { + return factor; + } + + public void setFactor(float factor) { + this.factor = factor; + } + + public void setMax(int max) { + this.max = max; + } + + public void setMin(int min) { + this.min = min; + } + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/misc/SwitchEntry.java b/naming/src/main/java/com/alibaba/nacos/naming/misc/SwitchEntry.java new file mode 100644 index 00000000000..13ef10001e4 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/misc/SwitchEntry.java @@ -0,0 +1,62 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.misc; + +/** + * @author dungu.zpf + */ +public class SwitchEntry { + + public static final String BATCH = "batch"; + public static final String DISTRO_THRESHOLD = "distroThreshold"; + public static final String ENABLE_ALL_DOM_NAME_CACHE = "enableAllDomNameCache"; + public static final String INCREMENTAL_LIST = "incrementalList"; + public static final String HEALTH_CHECK_WHITLE_LIST = "healthCheckWhiteList"; + public static final String CLIENT_BEAT_INTERVAL = "clientBeatInterval"; + public static final String PUSH_VERSION = "pushVersion"; + public static final String CLIENT_JAVA = "java"; + public static final String CLIENT_C = "c"; + public static final String CLIENT_PYTHON = "python"; + public static final String CLIENT_TENGINE = "python"; + public static final String TRAFFIC_SCHEDULING_VERSION = "trafficSchedulingVersion"; + public static final String PUSH_CACHE_MILLIS = "pushCacheMillis"; + public static final String DEFAULT_CACHE_MILLIS = "defaultCacheMillis"; + public static final String MASTERS = "masters"; + public static final String DISTRO = "distro"; + public static final String CHECK = "check"; + public static final String DOM_STATUS_SYNC_PERIOD = "domStatusSynchronizationPeriodMillis"; + public static final String SERVER_STATUS_SYNC_PERIOD = "serverStatusSynchronizationPeriodMillis"; + public static final String HEALTH_CHECK_TIMES = "healthCheckTimes"; + public static final String DISABLE_ADD_IP = "disableAddIP"; + public static final String ENABLE_CACHE = "enableCache"; + public static final String SEND_BEAT_ONLY = "sendBeatOnly"; + public static final String LIMITED_URL_MAP = "limitedUrlMap"; + public static final String ENABLE_STANDALONE = "enableStandalone"; + public static final int MIN_PUSH_CACHE_TIME_MIILIS = 10000; + public static final int MIN_CACHE_TIME_MIILIS = 1000; + public static final int MIN_DOM_SYNC_TIME_MIILIS = 5000; + public static final int MIN_SERVER_SYNC_TIME_MIILIS = 15000; + + public static final String ACTION_ADD = "add"; + public static final String ACTION_REPLACE = "replace"; + public static final String ACTION_VIEW = "view"; + public static final String ACTION_DELETE = "delete"; + public static final String ACTION_UPDATE = "update"; + public static final String ACTION_CLEAN = "clean"; + public static final String ACTION_OVERVIEW = "overview"; + + public static final String PARAM_JSON = "json"; +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/misc/Synchronizer.java b/naming/src/main/java/com/alibaba/nacos/naming/misc/Synchronizer.java new file mode 100644 index 00000000000..0d69646771b --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/misc/Synchronizer.java @@ -0,0 +1,38 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.misc; + +/** + * @author nacos + */ +public interface Synchronizer { + /** + * Send message to server + * + * @param serverIP target server address + * @param msg message to send + */ + void send(String serverIP, Message msg); + + /** + * Get message from server using message key + * + * @param serverIP source server address + * @param key message key + * @return message + */ + Message get(String serverIP, String key); +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/misc/UtilsAndCommons.java b/naming/src/main/java/com/alibaba/nacos/naming/misc/UtilsAndCommons.java new file mode 100644 index 00000000000..89797bb0e7e --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/misc/UtilsAndCommons.java @@ -0,0 +1,204 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.misc; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.parser.ParserConfig; +import com.alibaba.fastjson.serializer.SerializeConfig; +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.alibaba.nacos.naming.core.Domain; +import com.alibaba.nacos.naming.healthcheck.AbstractHealthCheckConfig; +import org.apache.commons.lang3.StringUtils; + +import java.io.File; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; + +/** + * @author nacos + */ +public class UtilsAndCommons { + + private static final String NACOS_CONF_DIR_PATH = System.getProperty("user.home") + "/conf"; + + private static final String NACOS_CONF_FILE_NAME = "cluster.conf"; + + private static String NACOS_CONF_FILE = NACOS_CONF_DIR_PATH + File.separator + NACOS_CONF_FILE_NAME; + + public static final String NACOS_SERVER_CONTEXT = "/nacos"; + + public static final String NACOS_SERVER_VERSION = "/v1"; + + public static final String NACOS_NAMING_CONTEXT = NACOS_SERVER_VERSION + "/ns"; + + public static final String NACOS_NAMING_INSTANCE_CONTEXT = "/instance"; + + public static final String NACOS_NAMING_RAFT_CONTEXT = "/raft"; + + public static final String NACOS_SERVER_HEADER = "Nacos-Server"; + + public static final String NACOS_VERSION = "1.0"; + + public static final String SUPER_TOKEN = "xy"; + + public static final String DOMAINS_DATA_ID = "com.alibaba.nacos.naming.domains.meta"; + + public static final String IPADDRESS_DATA_ID_PRE = "com.alibaba.nacos.naming.iplist."; + + static public final String NODE_TAG_IP_PRE = "com.alibaba.nacos.naming.tag.iplist."; + + public static final String TAG_DOMAINS_DATA_ID = "com.alibaba.nacos.naming.domains.tag.meta"; + + static public final String CIDR_REGEX = "[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}/[0-9]+"; + + static public final String UNKNOWN_SITE = "unknown"; + + static public final String UNKNOWN_HOST = "unknown"; + + public static final String DEFAULT_CLUSTER_NAME = "DEFAULT"; + + static public final String RAFT_DOM_PRE = "meta"; + static public final String RAFT_IPLIST_PRE = "iplist."; + static public final String RAFT_TAG_DOM_PRE = "tag.meta"; + static public final String RAFT_TAG_IPLIST_PRE = "tag.iplist."; + + public static final String SERVER_VERSION = NACOS_SERVER_HEADER + ":" + NACOS_VERSION; + + public static final String SELF_SERVICE_CLUSTER_ENV = "naming_self_service_cluster_ips"; + + public static final boolean STANDALONE_MODE = Boolean.parseBoolean(System.getProperty("nacos.standalone", "false")); + + public static final String CACHE_KEY_SPLITER = "@@@@"; + + public static final String LOCAL_HOST_IP = "127.0.0.1"; + + public static final String CLUSTER_CONF_IP_SPLITER = ":"; + + public static final int MAX_PUBLISH_WAIT_TIME_MILLIS = 5000; + + public static final String VERSION_STRING_SYNTAX = "[0-9]+\\.[0-9]+\\.[0-9]+"; + + public static final String API_UPDATE_SWITCH = "/api/updateSwitch"; + + public static final String API_SET_ALL_WEIGHTS = "/api/setWeight4AllIPs"; + + public static final String API_DOM_SERVE_STATUS = "/api/domServeStatus"; + + public static final String API_IP_FOR_DOM = "/api/ip4Dom"; + + public static final String API_DOM = "/api/dom"; + + public static final ScheduledExecutorService SERVER_STATUS_EXECUTOR; + + public static final ScheduledExecutorService DOMAIN_SYNCHRONIZATION_EXECUTOR; + + public static final ScheduledExecutorService DOMAIN_UPDATE_EXECUTOR; + + public static final ScheduledExecutorService INIT_CONFIG_EXECUTOR; + + static { + // custom serializer and deserializer for fast-json + SerializeConfig.getGlobalInstance() + .put(AbstractHealthCheckConfig.class, AbstractHealthCheckConfig.JsonAdapter.getInstance()); + ParserConfig.getGlobalInstance() + .putDeserializer(AbstractHealthCheckConfig.class, AbstractHealthCheckConfig.JsonAdapter.getInstance()); + + // write null values, otherwise will cause compatibility issues + JSON.DEFAULT_GENERATE_FEATURE |= SerializerFeature.WriteNullStringAsEmpty.getMask(); + JSON.DEFAULT_GENERATE_FEATURE |= SerializerFeature.WriteNullListAsEmpty.getMask(); + JSON.DEFAULT_GENERATE_FEATURE |= SerializerFeature.WriteNullBooleanAsFalse.getMask(); + JSON.DEFAULT_GENERATE_FEATURE |= SerializerFeature.WriteMapNullValue.getMask(); + JSON.DEFAULT_GENERATE_FEATURE |= SerializerFeature.WriteNullNumberAsZero.getMask(); + + String nacosHome = System.getProperty("nacos.home"); + + if (StringUtils.isNotBlank(nacosHome)) { + NACOS_CONF_FILE = nacosHome + File.separator + "conf" + File.separator + NACOS_CONF_FILE_NAME; + } + + DOMAIN_SYNCHRONIZATION_EXECUTOR + = new ScheduledThreadPoolExecutor(1, new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setName("nacos.naming.domains.worker"); + t.setDaemon(true); + return t; + } + }); + + DOMAIN_UPDATE_EXECUTOR + = new ScheduledThreadPoolExecutor(1, new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setName("nacos.naming.domains.update.processor"); + t.setDaemon(true); + return t; + } + }); + + INIT_CONFIG_EXECUTOR + = new ScheduledThreadPoolExecutor(1, new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setName("nacos.naming.init.config.worker"); + t.setDaemon(true); + return t; + } + }); + + SERVER_STATUS_EXECUTOR + = new ScheduledThreadPoolExecutor(1, new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setName("nacos.naming.status.worker"); + t.setDaemon(true); + return t; + } + }); + + } + + public static String getAllExceptionMsg(Throwable e) { + Throwable cause = e; + StringBuilder strBuilder = new StringBuilder(); + + while (cause != null && !StringUtils.isEmpty(cause.getMessage())) { + strBuilder.append("caused: ").append(cause.getMessage()).append(";"); + cause = cause.getCause(); + } + + return strBuilder.toString(); + } + + public static String getConfFile() { + return NACOS_CONF_FILE; + } + + + public static String getIPListStoreKey(Domain dom) { + return UtilsAndCommons.IPADDRESS_DATA_ID_PRE + dom.getName(); + } + + public static String getDomStoreKey(Domain dom) { + return UtilsAndCommons.DOMAINS_DATA_ID + "." + dom.getName(); + } + +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/monitor/PerformanceLoggerThread.java b/naming/src/main/java/com/alibaba/nacos/naming/monitor/PerformanceLoggerThread.java new file mode 100644 index 00000000000..a9c604c80a0 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/monitor/PerformanceLoggerThread.java @@ -0,0 +1,142 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.monitor; + +import com.alibaba.nacos.naming.core.DomainsManager; +import com.alibaba.nacos.naming.misc.Loggers; +import com.alibaba.nacos.naming.misc.Switch; +import com.alibaba.nacos.naming.push.PushService; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.ArrayList; +import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +/** + * @author nacos + */ +public class PerformanceLoggerThread { + + @Autowired + private DomainsManager domainsManager; + + private ScheduledExecutorService executor = new ScheduledThreadPoolExecutor(1, new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setDaemon(true); + t.setName("nacos-server-performance"); + return t; + } + }); + + private static final long PERIOD = 1 * 60 * 60; + private static final long HEALTH_CHECK_PERIOD = 5 * 60; + + public void init(DomainsManager domainsManager) { + this.domainsManager = domainsManager; + start(); + } + + private void freshHealthCheckSwitch() { + Loggers.SRV_LOG.info("HEALTH-CHECK", "health check is " + Switch.isHealthCheckEnabled()); + } + + class HealthCheckSwitchTask implements Runnable { + + @Override + public void run() { + try { + freshHealthCheckSwitch(); + } catch (Exception ignore) { + + } + } + } + + private void start() { + PerformanceLogTask task = new PerformanceLogTask(); + executor.scheduleWithFixedDelay(task, 30, PERIOD, TimeUnit.SECONDS); + executor.scheduleWithFixedDelay(new HealthCheckSwitchTask(), 30, HEALTH_CHECK_PERIOD, TimeUnit.SECONDS); + executor.scheduleWithFixedDelay(new AllDomNamesTask(), 60, 60, TimeUnit.SECONDS); + + } + + class AllDomNamesTask implements Runnable { + + @Override + public void run() { + try { + domainsManager.setAllDomNames(new ArrayList(domainsManager.getAllDomNames())); + Loggers.PERFORMANCE_LOG.debug("refresh all dom names: " + domainsManager.getAllDomNamesCache().size()); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + class PerformanceLogTask implements Runnable { + + @Override + public void run() { + try { + int domCount = domainsManager.getDomCount(); + int ipCount = domainsManager.getIPCount(); + long maxPushMaxCost = getMaxPushCost(); + long avgPushCost = getAvgPushCost(); + Loggers.PERFORMANCE_LOG.info("PERFORMANCE:" + "|" + domCount + "|" + ipCount + "|" + maxPushMaxCost + "|" + avgPushCost); + } catch (Exception e) { + Loggers.SRV_LOG.warn("PERFORMANCE", "Exception while print performance log.", e); + } + + } + } + + private long getMaxPushCost() { + long max = -1; + + for (Map.Entry entry : PushService.pushCostMap.entrySet()) { + if (entry.getValue() > max) { + max = entry.getValue(); + } + } + + return max; + } + + private long getAvgPushCost() { + int size = 0; + long totalCost = 0; + long avgCost = -1; + + for (Map.Entry entry : PushService.pushCostMap.entrySet()) { + size += 1; + totalCost += entry.getValue(); + } + PushService.pushCostMap.clear(); + + if (size > 0 && totalCost > 0) { + avgCost = totalCost / size; + } + return avgCost; + } + + public static void main(String[] args) { + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/push/ClientInfo.java b/naming/src/main/java/com/alibaba/nacos/naming/push/ClientInfo.java new file mode 100644 index 00000000000..8995ba52acc --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/push/ClientInfo.java @@ -0,0 +1,145 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.push; + +import com.alibaba.nacos.naming.misc.UtilsAndCommons; +import org.apache.commons.lang3.StringUtils; +import org.codehaus.jackson.Version; +import org.codehaus.jackson.util.VersionUtil; + +/** + * @author nacos + */ +public class ClientInfo { + public Version version = Version.unknownVersion(); + public ClientType type = ClientType.UNKNOWN; + + public ClientInfo(String userAgent) { + String versionStr = StringUtils.isEmpty(userAgent) ? StringUtils.EMPTY : userAgent; + + if (versionStr.startsWith(ClientTypeDescription.JAVA_CLIENT)) { + type = ClientType.JAVA; + + versionStr = versionStr.substring(versionStr.indexOf(":v") + 2, versionStr.length()); + version = VersionUtil.parseVersion(versionStr); + + return; + } + + if (versionStr.startsWith(ClientTypeDescription.DNSF_CLIENT)) { + type = ClientType.DNS; + + versionStr = versionStr.substring(versionStr.indexOf(":v") + 2, versionStr.length()); + version = VersionUtil.parseVersion(versionStr); + + return; + } + + if (versionStr.startsWith(ClientTypeDescription.C_CLIENT)) { + type = ClientType.C; + + versionStr = versionStr.substring(versionStr.indexOf(":v") + 2, versionStr.length()); + version = VersionUtil.parseVersion(versionStr); + + return; + } + + if (versionStr.startsWith(ClientTypeDescription.SDK_CLIENT)) { + type = ClientType.JAVA_SDK; + + versionStr = versionStr.substring(versionStr.indexOf(":v") + 2, versionStr.length()); + version = VersionUtil.parseVersion(versionStr); + + return; + } + + if (versionStr.startsWith(UtilsAndCommons.NACOS_SERVER_HEADER)) { + type = ClientType.NACOS_SERVER; + + versionStr = versionStr.substring(versionStr.indexOf(":v") + 2, versionStr.length()); + version = VersionUtil.parseVersion(versionStr); + + return; + } + + if (versionStr.startsWith(ClientTypeDescription.NGINX_CLIENT)) { + type = ClientType.TENGINE; + + versionStr = versionStr.substring(versionStr.indexOf(":v") + 2, versionStr.length()); + version = VersionUtil.parseVersion(versionStr); + + return; + } + + if (versionStr.startsWith(ClientTypeDescription.CPP_CLIENT)) { + type = ClientType.C; + + versionStr = versionStr.substring(versionStr.indexOf(":v") + 2, versionStr.length()); + version = VersionUtil.parseVersion(versionStr); + + return; + } + + //we're not eager to implement other type yet + this.type = ClientType.UNKNOWN; + this.version = Version.unknownVersion(); + } + + public enum ClientType { + /** + * Java client type + */ + JAVA, + /** + * C client type + */ + C, + /** + * php client type + */ + PHP, + /** + * dns-f client type + */ + DNS, + /** + * nginx client type + */ + TENGINE, + /** + * sdk client type + */ + JAVA_SDK, + /** + * Server notify each other + */ + NACOS_SERVER, + /** + * Unknown client type + */ + UNKNOWN; + } + + public static class ClientTypeDescription { + public static final String JAVA_CLIENT = "VIPServer-Java-Client"; + public static final String DNSF_CLIENT = "VIPSRV-DNS"; + public static final String C_CLIENT = "VIPServer-C-Client"; + public static final String SDK_CLIENT = "VIPServer-SDK-Java"; + public static final String NGINX_CLIENT = "unit-nginx"; + public static final String CPP_CLIENT = "vip-client4cpp"; + } + +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/push/DataSource.java b/naming/src/main/java/com/alibaba/nacos/naming/push/DataSource.java new file mode 100644 index 00000000000..b200badfa6d --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/push/DataSource.java @@ -0,0 +1,30 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.push; + +/** + * @author nacos + */ +public interface DataSource { + /** + * Get push data for a specified client + * + * @param client target client + * @return data to push + * @throws Exception + */ + String getData(PushService.PushClient client) throws Exception; +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/push/PushService.java b/naming/src/main/java/com/alibaba/nacos/naming/push/PushService.java new file mode 100644 index 00000000000..a36da06b086 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/push/PushService.java @@ -0,0 +1,681 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.push; + +import com.alibaba.fastjson.JSON; +import com.alibaba.nacos.naming.misc.Loggers; +import com.alibaba.nacos.naming.misc.Switch; +import com.alibaba.nacos.naming.misc.UtilsAndCommons; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.StringUtils; +import org.codehaus.jackson.util.VersionUtil; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetSocketAddress; +import java.net.SocketException; +import java.nio.charset.Charset; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.zip.GZIPOutputStream; + +/** + * @author nacos + */ +public class PushService { + + public static final long ACK_TIMEOUT_NANOS = TimeUnit.SECONDS.toNanos(10L); + private static final int MAX_RETRY_TIMES = 1; + private static BlockingQueue QUEUE = new LinkedBlockingDeque(); + + private static volatile ConcurrentMap ackMap + = new ConcurrentHashMap(); + + private static ConcurrentMap> clientMap + = new ConcurrentHashMap>(); + + private static volatile ConcurrentHashMap udpSendTimeMap = new ConcurrentHashMap(); + + public static volatile ConcurrentHashMap pushCostMap = new ConcurrentHashMap(); + + private static int totalPush = 0; + + private static int failedPush = 0; + + private static ConcurrentHashMap lastPushMillisMap = new ConcurrentHashMap<>(); + + private static DatagramSocket udpSocket; + + private static Map futureMap = new ConcurrentHashMap<>(); + private static ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setDaemon(true); + t.setName("com.alibaba.nacos.naming.push.retransmitter"); + return t; + } + }); + + private static ScheduledExecutorService udpSender = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setDaemon(true); + t.setName("com.alibaba.nacos.naming.push.udpSender"); + return t; + } + }); + + + static { + try { + udpSocket = new DatagramSocket(); + + Sender sender; + Receiver receiver; + + sender = new Sender(); + + Thread outThread; + Thread inThread; + + outThread = new Thread(sender); + outThread.setDaemon(true); + outThread.setName("com.alibaba.nacos.naming.push.sender"); + outThread.start(); + + receiver = new Receiver(); + + inThread = new Thread(receiver); + inThread.setDaemon(true); + inThread.setName("com.alibaba.nacos.naming.push.receiver"); + inThread.start(); + + executorService.scheduleWithFixedDelay(new Runnable() { + @Override + public void run() { + try { + removeClientIfZombie(); + } catch (Throwable e) { + Loggers.PUSH.warn("VIPSRV-PUSH", "failed to remove client zombied"); + } + } + }, 0, 20, TimeUnit.SECONDS); + + } catch (SocketException e) { + Loggers.SRV_LOG.error("VIPSRV-PUSH", "failed to init push service"); + } + } + + public static int getTotalPush() { + return totalPush; + } + + public static void addClient(String dom, + String clusters, + String agent, + InetSocketAddress socketAddr, + DataSource dataSource, + String tenant, + String app) { + + PushClient client = new PushService.PushClient(dom, + clusters, + agent, + socketAddr, + dataSource, + tenant, + app); + addClient(client); + } + + public static void addClient(PushClient client) { + // client is stored by key 'dom' because notify event is driven by dom change + ConcurrentMap clients = clientMap.get(client.getDom()); + if (clients == null) { + clientMap.putIfAbsent(client.getDom(), new ConcurrentHashMap(1024)); + clients = clientMap.get(client.getDom()); + } + + PushClient oldClient = clients.get(client.toString()); + if (oldClient != null) { + oldClient.refresh(); + } else { + PushClient res = clients.putIfAbsent(client.toString(), client); + if (res != null) { + Loggers.PUSH.warn("client:" + res.getAddrStr() + " already associated with key " + res.toString()); + } + Loggers.PUSH.debug("client: " + client.getAddrStr() + " added for dom: " + client.getDom()); + } + } + + public static void removeClientIfZombie() { + + int size = 0; + for (Map.Entry> entry : clientMap.entrySet()) { + ConcurrentMap clientConcurrentMap = entry.getValue(); + for (Map.Entry entry1 : clientConcurrentMap.entrySet()) { + PushClient client = entry1.getValue(); + if (client.zombie()) { + clientConcurrentMap.remove(entry1.getKey()); + } + } + + size += clientConcurrentMap.size(); + } + + Loggers.PUSH.info("VIPSRV-PUSH", "clientMap size: " + size); + + } + + private static Receiver.AckEntry prepareAckEntry(PushClient client, byte[] dataBytes, Map data, + long lastRefTime) { + String key = getACKKey(client.getSocketAddr().getAddress().getHostAddress(), + client.getSocketAddr().getPort(), + lastRefTime); + DatagramPacket packet = null; + try { + packet = new DatagramPacket(dataBytes, dataBytes.length, client.socketAddr); + Receiver.AckEntry ackEntry = new Receiver.AckEntry(key, packet); + ackEntry.data = data; + + // we must store the key be fore send, otherwise there will be a chance the + // ack returns before we put in + ackEntry.data = data; + + return ackEntry; + } catch (Exception e) { + Loggers.PUSH.error("VIPSRV-PUSH", "failed to prepare data: [" + data + "] to client: [" + + client.getSocketAddr() + "]", e); + } + + return null; + } + + public static String getPushCacheKey(String dom, String clientIP, String agent) { + return dom + UtilsAndCommons.CACHE_KEY_SPLITER + agent; + } + + public static void domChanged(final String dom) { + if (futureMap.containsKey(dom)) { + return; + } + Future future = udpSender.schedule(new Runnable() { + @Override + public void run() { + try { + Loggers.PUSH.info(dom + " is changed, add it to push queue."); + ConcurrentMap clients = clientMap.get(dom); + if (MapUtils.isEmpty(clients)) { + return; + } + + Map cache = new HashMap<>(16); + long lastRefTime = System.nanoTime(); + for (PushClient client : clients.values()) { + if (client.zombie()) { + Loggers.PUSH.debug("client is zombie: " + client.toString()); + clients.remove(client.toString()); + Loggers.PUSH.debug("client is zombie: " + client.toString()); + continue; + } + + Receiver.AckEntry ackEntry; + Loggers.PUSH.debug("push dom: " + dom + " to cleint: " + client.toString()); + String key = getPushCacheKey(dom, client.getIp(), client.getAgent()); + byte[] compressData = null; + Map data = null; + if (Switch.getPushCacheMillis() >= 20000 && cache.containsKey(key)) { + org.javatuples.Pair pair = (org.javatuples.Pair) cache.get(key); + compressData = (byte[]) (pair.getValue0()); + data = (Map) pair.getValue1(); + + Loggers.PUSH.debug("PUSH-CACHE", "cache hit: " + dom + ":" + client.getAddrStr()); + } + + if (compressData != null) { + ackEntry = prepareAckEntry(client, compressData, data, lastRefTime); + } else { + ackEntry = prepareAckEntry(client, prepareHostsData(client), lastRefTime); + if (ackEntry != null) { + cache.put(key, new org.javatuples.Pair<>(ackEntry.origin.getData(), ackEntry.data)); + } + } + + Loggers.PUSH.info("dom: " + client.getDom() + " changed, schedule push for: " + + client.getAddrStr() + ", agent: " + client.getAgent() + ", key: " + + (ackEntry == null ? null : ackEntry.key)); + + udpPush(ackEntry); + } + } catch (Exception e) { + Loggers.PUSH.error("VIPSRV-PUSH", "failed to push dom: " + dom + " to cleint", e); + + } finally { + futureMap.remove(dom); + } + + } + }, 1000, TimeUnit.MILLISECONDS); + + futureMap.put(dom, future); + } + + public static boolean canEnablePush(String agent) { + ClientInfo clientInfo = new ClientInfo(agent); + + if (ClientInfo.ClientType.JAVA == clientInfo.type + && clientInfo.version.compareTo(VersionUtil.parseVersion(Switch.getPushJavaVersion())) >= 0) { + + return true; + } else if (ClientInfo.ClientType.DNS == clientInfo.type + && clientInfo.version.compareTo(VersionUtil.parseVersion(Switch.getPushPythonVersion())) >= 0) { + return true; + } else if (ClientInfo.ClientType.C == clientInfo.type + && clientInfo.version.compareTo(VersionUtil.parseVersion(Switch.getPushCVersion())) >= 0) { + return true; + } + return false; + } + + public static List getFailedPushes() { + return new ArrayList(ackMap.values()); + } + + public static int getFailedPushCount() { + return ackMap.size() + failedPush; + } + + public static void resetPushState() { + ackMap.clear(); + } + + public static class PushClient { + private String dom; + private String clusters; + private String agent; + private String tenant; + private String app; + private InetSocketAddress socketAddr; + private DataSource dataSource; + private Map params; + + public Map getParams() { + return params; + } + + public void setParams(Map params) { + this.params = params; + } + + public long lastRefTime = System.currentTimeMillis(); + + public PushClient(String dom + , String clusters + , String agent + , InetSocketAddress socketAddr + , DataSource dataSource) { + this.dom = dom; + this.clusters = clusters; + this.agent = agent; + this.socketAddr = socketAddr; + this.dataSource = dataSource; + } + + public PushClient(String dom, + String clusters, + String agent, + InetSocketAddress socketAddr, + DataSource dataSource, + String tenant, + String app) { + this.dom = dom; + this.clusters = clusters; + this.agent = agent; + this.socketAddr = socketAddr; + this.dataSource = dataSource; + this.tenant = tenant; + this.app = app; + } + + public DataSource getDataSource() { + return dataSource; + } + + public PushClient(InetSocketAddress socketAddr) { + this.socketAddr = socketAddr; + } + + public boolean zombie() { + return System.currentTimeMillis() - lastRefTime > Switch.getPushCacheMillis(dom); + } + + @Override + public String toString() { + return "dom: " + dom + + ", clusters: " + clusters + + ", ip: " + socketAddr.getAddress().getHostAddress() + + ", port: " + socketAddr.getPort() + + ", agent: " + agent; + } + + public String getAgent() { + return agent; + } + + public String getAddrStr() { + return socketAddr.getAddress().getHostAddress() + ":" + socketAddr.getPort(); + } + + public String getIp() { + return socketAddr.getAddress().getHostAddress(); + } + + @Override + public int hashCode() { + return Objects.hash(dom, clusters, socketAddr); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof PushClient)) { + return false; + } + + PushClient other = (PushClient) obj; + + return dom.equals(other.dom) && clusters.equals(other.clusters) && socketAddr.equals(other.socketAddr); + } + + public String getClusters() { + return clusters; + } + + public void setClusters(String clusters) { + this.clusters = clusters; + } + + public String getDom() { + return dom; + } + + public void setDom(String dom) { + this.dom = dom; + } + + public String getTenant() { + return tenant; + } + + public void setTenant(String tenant) { + this.tenant = tenant; + } + + public String getApp() { + return app; + } + + public void setApp(String app) { + this.app = app; + } + + public InetSocketAddress getSocketAddr() { + return socketAddr; + } + + public void refresh() { + lastRefTime = System.currentTimeMillis(); + } + } + + private static byte[] compressIfNecessary(byte[] dataBytes) throws IOException { + // enable compression when data is larger than 1KB + int maxDataSizeUncompress = 1024; + if (dataBytes.length < maxDataSizeUncompress) { + return dataBytes; + } + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + GZIPOutputStream gzip = new GZIPOutputStream(out); + gzip.write(dataBytes); + gzip.close(); + + return out.toByteArray(); + } + + private static Map prepareHostsData(PushClient client) throws Exception { + Map cmd = new HashMap(2); + cmd.put("type", "dom"); + cmd.put("data", client.getDataSource().getData(client)); + + return cmd; + } + + private static Receiver.AckEntry prepareAckEntry(PushClient client, Map data, long lastRefTime) { + if (MapUtils.isEmpty(data)) { + Loggers.PUSH.error("VIPSRV-PUSH", "pushing empty data for client is not allowed: " + client); + return null; + } + + data.put("lastRefTime", lastRefTime); + + // we apply lastRefTime as sequence num for further ack + String key = getACKKey(client.getSocketAddr().getAddress().getHostAddress(), + client.getSocketAddr().getPort(), + lastRefTime); + + String dataStr = JSON.toJSONString(data); + + try { + byte[] dataBytes = dataStr.getBytes("UTF-8"); + dataBytes = compressIfNecessary(dataBytes); + + DatagramPacket packet = new DatagramPacket(dataBytes, dataBytes.length, client.socketAddr); + + // we must store the key be fore send, otherwise there will be a chance the + // ack returns before we put in + Receiver.AckEntry ackEntry = new Receiver.AckEntry(key, packet); + ackEntry.data = data; + + return ackEntry; + } catch (Exception e) { + Loggers.PUSH.error("VIPSRV-PUSH", "failed to prepare data: [" + data + "] to client: [" + + client.getSocketAddr() + "]", e); + return null; + } + } + + private static Receiver.AckEntry udpPush(Receiver.AckEntry ackEntry) { + if (ackEntry == null) { + Loggers.PUSH.error("VIPSRV-PUSH", "ackEntry is null "); + return null; + } + + if (ackEntry.getRetryTimes() > MAX_RETRY_TIMES) { + Loggers.PUSH.warn("max re-push times reached, retry times " + ackEntry.retryTimes + ", key: " + ackEntry.key); + ackMap.remove(ackEntry.key); + failedPush += 1; + return ackEntry; + } + + try { + if (!ackMap.containsKey(ackEntry.key)) { + totalPush++; + } + ackMap.put(ackEntry.key, ackEntry); + udpSendTimeMap.put(ackEntry.key, System.currentTimeMillis()); + + Loggers.PUSH.info("send udp packet: " + ackEntry.key); + udpSocket.send(ackEntry.origin); + + ackEntry.increaseRetryTime(); + + executorService.schedule(new Retransmitter(ackEntry), TimeUnit.NANOSECONDS.toMillis(ACK_TIMEOUT_NANOS), + TimeUnit.MILLISECONDS); + + return ackEntry; + } catch (Exception e) { + Loggers.PUSH.error("VIPSRV-PUSH", "failed to push data: [" + ackEntry.data + "] to client: [" + + ackEntry.origin.getAddress().getHostAddress() + "]", e); + ackMap.remove(ackEntry.key); + failedPush += 1; + + return null; + } + } + + private static class Sender implements Runnable { + @Override + public void run() { + while (true) { + try { + String dom; + try { + dom = QUEUE.take(); + } catch (InterruptedException e) { + continue; //ignore + } + + if (System.currentTimeMillis() - lastPushMillisMap.get(dom) < 1000) { + QUEUE.add(dom); + continue; + } + + lastPushMillisMap.put(dom, System.currentTimeMillis()); + + ConcurrentMap clients = clientMap.get(dom); + if (MapUtils.isEmpty(clients)) { + continue; + } + + for (PushClient client : clients.values()) { + if (client.zombie()) { + clients.remove(client.toString()); + continue; + } + Loggers.PUSH.debug("push dom: " + dom + " to cleint"); + Receiver.AckEntry ackEntry = prepareAckEntry(client, prepareHostsData(client), System.nanoTime()); + Loggers.PUSH.info("sender", "dom: " + client.getDom() + " changed, schedule push for: " + + client.getAddrStr() + ", agent: " + client.getAgent() + ", key: " + ackEntry.key); + udpPush(ackEntry); + } + } catch (Throwable t) { + Loggers.PUSH.error("VIPSRV-PUSH", "failed, caused by: ", t); + } + } + } + } + + private static String getACKKey(String host, int port, long lastRefTime) { + return StringUtils.strip(host) + "," + port + "," + lastRefTime; + } + + public static class Retransmitter implements Runnable { + Receiver.AckEntry ackEntry; + + public Retransmitter(Receiver.AckEntry ackEntry) { + this.ackEntry = ackEntry; + } + + @Override + public void run() { + if (ackMap.containsKey(ackEntry.key)) { + Loggers.PUSH.info("retry to push data, key: " + ackEntry.key); + udpPush(ackEntry); + } + } + } + + public static class Receiver implements Runnable { + @Override + public void run() { + while (true) { + byte[] buffer = new byte[1024 * 64]; + DatagramPacket packet = new DatagramPacket(buffer, buffer.length); + + try { + udpSocket.receive(packet); + + String json = new String(packet.getData(), 0, packet.getLength(), Charset.forName("UTF-8")).trim(); + AckPacket ackPacket = JSON.parseObject(json, AckPacket.class); + + InetSocketAddress socketAddress = (InetSocketAddress) packet.getSocketAddress(); + String ip = socketAddress.getAddress().getHostAddress(); + int port = socketAddress.getPort(); + + if (System.nanoTime() - ackPacket.lastRefTime > ACK_TIMEOUT_NANOS) { + Loggers.PUSH.warn("ack takes too long from" + packet.getSocketAddress() + + " ack json: " + json); + } + + String ackKey = getACKKey(ip, port, ackPacket.lastRefTime); + AckEntry ackEntry = ackMap.remove(ackKey); + if (ackEntry == null) { + throw new IllegalStateException("unable to find ackEntry for key: " + ackKey + + ", ack json: " + json); + } + + long pushCost = System.currentTimeMillis() - udpSendTimeMap.get(ackKey); + + Loggers.PUSH.info("received ack: " + json + " from: " + ip + + ":" + port + ", cost: " + pushCost + "ms" + ", unacked: " + ackMap.size() + + ",total push: " + totalPush); + + pushCostMap.put(ackKey, pushCost); + + udpSendTimeMap.remove(ackKey); + + } catch (Throwable e) { + Loggers.PUSH.error("VIPSRV-PUSH", "error while receiving ack data", e); + } + } + } + + public static class AckEntry { + + public AckEntry(String key, DatagramPacket packet) { + this.key = key; + this.origin = packet; + } + + public void increaseRetryTime() { + retryTimes.incrementAndGet(); + } + + public int getRetryTimes() { + return retryTimes.get(); + } + + public String key; + public DatagramPacket origin; + private AtomicInteger retryTimes = new AtomicInteger(0); + public Map data; + } + + public static class AckPacket { + public String type; + public long lastRefTime; + + public String data; + } + } + + +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/raft/Datum.java b/naming/src/main/java/com/alibaba/nacos/naming/raft/Datum.java new file mode 100644 index 00000000000..89aee849db6 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/raft/Datum.java @@ -0,0 +1,27 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.raft; + +/** + * @author nacos + */ +public class Datum { + public String key; + + public String value; + + public long timestamp; +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/raft/GlobalExecutor.java b/naming/src/main/java/com/alibaba/nacos/naming/raft/GlobalExecutor.java new file mode 100644 index 00000000000..7e2af78d964 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/raft/GlobalExecutor.java @@ -0,0 +1,58 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.raft; + +import java.util.concurrent.*; + +/** + * @author nacos + */ +public class GlobalExecutor { + public static final long HEARTBEAT_INTVERAL_MS = TimeUnit.SECONDS.toMillis(5L); + + public static final long LEADER_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(15L); + + public static final long RAMDOM_MS = TimeUnit.SECONDS.toMillis(5L); + + public static final long TICK_PERIOD_MS = TimeUnit.MILLISECONDS.toMillis(500L); + + public static final long ADDRESS_SERVER_UPDATE_INTVERAL_MS = TimeUnit.SECONDS.toMillis(5L); + + private static ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(2, new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + + t.setDaemon(true); + t.setName("com.alibaba.nacos.naming.raft.timer"); + + return t; + } + }); + + + public static void register(Runnable runnable) { + executorService.scheduleAtFixedRate(runnable, 0, TICK_PERIOD_MS, TimeUnit.MILLISECONDS); + } + + public static void register1(Runnable runnable) { + executorService.scheduleWithFixedDelay(runnable, 0, TICK_PERIOD_MS, TimeUnit.MILLISECONDS); + } + + public static void register(Runnable runnable, long delay) { + executorService.scheduleAtFixedRate(runnable, 0, delay, TimeUnit.MILLISECONDS); + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/raft/PeerSet.java b/naming/src/main/java/com/alibaba/nacos/naming/raft/PeerSet.java new file mode 100644 index 00000000000..97af12c79c0 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/raft/PeerSet.java @@ -0,0 +1,221 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.raft; + +import com.alibaba.fastjson.JSON; +import com.alibaba.nacos.naming.misc.HttpClient; +import com.alibaba.nacos.naming.misc.Loggers; +import com.alibaba.nacos.naming.misc.NetUtils; +import com.alibaba.nacos.naming.misc.UtilsAndCommons; +import com.ning.http.client.AsyncCompletionHandler; +import com.ning.http.client.Response; +import org.apache.commons.collections.SortedBag; +import org.apache.commons.collections.bag.TreeBag; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; + +import java.net.HttpURLConnection; +import java.util.*; + +/** + * @author nacos + */ +public class PeerSet { + + private RaftPeer leader = null; + + private static Map peers = new HashMap(); + + private static Set sites = new HashSet<>(); + + public PeerSet() { + } + + public RaftPeer getLeader() { + if (UtilsAndCommons.STANDALONE_MODE) { + return local(); + } + return leader; + } + + public Set allSites() { + return sites; + } + + public void add(List servers) { + for (String server : servers) { + RaftPeer peer = new RaftPeer(); + peer.ip = server; + + peers.put(server, peer); + } + + if (UtilsAndCommons.STANDALONE_MODE) { + RaftPeer local = local(); + local.state = RaftPeer.State.LEADER; + local.voteFor = NetUtils.localIP(); + + } + } + + public void remove(List servers) { + for (String server : servers) { + peers.remove(server); + } + } + + public RaftPeer update(RaftPeer peer) { + peers.put(peer.ip, peer); + return peer; + } + + public boolean isLeader(String ip) { + if (UtilsAndCommons.STANDALONE_MODE) { + return true; + } + + Loggers.RAFT.info("IS LEADER", "leader: " + leader.ip + ", ip: " + ip); + + return StringUtils.equals(leader.ip, ip); + } + + public Set allServersIncludeMyself() { + return peers.keySet(); + } + + public Set allServersWithoutMySelf() { + Set servers = new HashSet(peers.keySet()); + + // exclude myself + servers.remove(local().ip); + + return servers; + } + + public Collection allPeers() { + return peers.values(); + } + + public int size() { + return peers.size(); + } + + public RaftPeer decideLeader(RaftPeer candidate) { + peers.put(candidate.ip, candidate); + + SortedBag ips = new TreeBag(); + for (RaftPeer peer : peers.values()) { + if (StringUtils.isEmpty(peer.voteFor)) { + continue; + } + + ips.add(peer.voteFor); + } + + String first = (String) ips.last(); + if (ips.getCount(first) >= majorityCount()) { + RaftPeer peer = peers.get(first); + peer.state = RaftPeer.State.LEADER; + + if (!ObjectUtils.equals(leader, peer)) { + leader = peer; + Loggers.RAFT.info(leader.ip + " has become the LEADER"); + } + } + + return leader; + } + + public RaftPeer makeLeader(RaftPeer candidate) { + if (!ObjectUtils.equals(leader, candidate)) { + leader = candidate; + Loggers.RAFT.info(leader.ip + " has become the LEADER" + ",local :" + JSON.toJSONString(local()) + ", leader: " + JSON.toJSONString(leader)); + } + + for (final RaftPeer peer : peers.values()) { + Map params = new HashMap(1); + if (!ObjectUtils.equals(peer, candidate) && peer.state == RaftPeer.State.LEADER) { + try { + String url = RaftCore.buildURL(peer.ip, RaftCore.API_GET_PEER); + HttpClient.asyncHttpPost(url, null, params, new AsyncCompletionHandler() { + @Override + public Integer onCompleted(Response response) throws Exception { + if (response.getStatusCode() != HttpURLConnection.HTTP_OK) { + Loggers.RAFT.error("VIPSRV-RAFT", "get peer failed: " + response.getResponseBody() + ", peer: " + peer.ip); + peer.state = RaftPeer.State.FOLLOWER; + return 1; + } + + update(JSON.parseObject(response.getResponseBody(), RaftPeer.class)); + + return 0; + } + }); + } catch (Exception e) { + peer.state = RaftPeer.State.FOLLOWER; + Loggers.RAFT.error("VIPSRV-RAFT", "error while getting peer from peer: " + peer.ip); + } + } + } + + return update(candidate); + } + + public RaftPeer local() { + RaftPeer peer = peers.get(NetUtils.localIP()); + if (peer == null) { + throw new IllegalStateException("unable to find local peer: " + NetUtils.localIP() + ", all peers: " + + Arrays.toString(peers.keySet().toArray())); + } + + return peer; + } + + public RaftPeer get(String server) { + return peers.get(server); + } + + public int majorityCount() { + return peers.size() / 2 + 1; + } + + public void reset() { + + leader = null; + + for (RaftPeer peer : peers.values()) { + peer.voteFor = null; + } + } + + public void setTerm(long term) { + RaftPeer local = local(); + + if (term < local.term.get()) { + return; + } + + local.term.set(term); + } + + public long getTerm() { + return local().term.get(); + } + + public boolean contains(RaftPeer remote) { + return peers.containsKey(remote.ip); + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/raft/RaftCore.java b/naming/src/main/java/com/alibaba/nacos/naming/raft/RaftCore.java new file mode 100644 index 00000000000..43af2a0dbe0 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/raft/RaftCore.java @@ -0,0 +1,1015 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.raft; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.TypeReference; +import com.alibaba.nacos.naming.boot.RunningConfig; +import com.alibaba.nacos.naming.core.VirtualClusterDomain; +import com.alibaba.nacos.naming.misc.*; +import com.ning.http.client.AsyncCompletionHandler; +import com.ning.http.client.Response; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.javatuples.Pair; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.zip.GZIPOutputStream; + +/** + * @author nacos + */ +public class RaftCore { + + public static final String API_VOTE = UtilsAndCommons.NACOS_NAMING_CONTEXT + "/raft/vote"; + + public static final String API_BEAT = UtilsAndCommons.NACOS_NAMING_CONTEXT + "/raft/beat"; + + public static final String API_PUB = UtilsAndCommons.NACOS_NAMING_CONTEXT + "/raft/publish"; + + public static final String API_UNSF_PUB = UtilsAndCommons.NACOS_NAMING_CONTEXT + "/raft/unSafePublish"; + + public static final String API_DEL = UtilsAndCommons.NACOS_NAMING_CONTEXT + "/raft/delete"; + + public static final String API_GET = UtilsAndCommons.NACOS_NAMING_CONTEXT + "/raft/get"; + + public static final String API_ON_PUB = UtilsAndCommons.NACOS_NAMING_CONTEXT + "/raft/onPublish"; + + public static final String API_ON_DEL = UtilsAndCommons.NACOS_NAMING_CONTEXT + "/raft/onDelete"; + + public static final String API_GET_PEER = UtilsAndCommons.NACOS_NAMING_CONTEXT + "/raft/getPeer"; + + private static ScheduledExecutorService executor = new ScheduledThreadPoolExecutor(1, new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + + t.setDaemon(true); + t.setName("com.alibaba.nacos.naming.raft.notifier"); + + return t; + } + }); + + public static final Lock OPERATE_LOCK = new ReentrantLock(); + + public static final int PUBLISH_TERM_INCREASE_COUNT = 100; + + private static final int INIT_LOCK_TIME_SECONDS = 3; + + private static volatile boolean initialized = false; + + private static Lock lock = new ReentrantLock(); + + private static volatile List listeners = new CopyOnWriteArrayList<>(); + + private static ConcurrentMap datums = new ConcurrentHashMap(); + + private static PeerSet peers = new PeerSet(); + + private static volatile Notifier notifier = new Notifier(); + + public static void init() throws Exception { + + Loggers.RAFT.info("initializing Raft sub-system"); + + executor.submit(notifier); + + peers.add(NamingProxy.getServers()); + + long start = System.currentTimeMillis(); + + RaftStore.load(); + + Loggers.RAFT.info("cache loaded, peer count: " + peers.size() + + ", datum count: " + datums.size() + + ", current term:" + peers.getTerm()); + + while (true) { + if (notifier.tasks.size() <= 0) { + break; + } + Thread.sleep(1000L); + System.out.println(notifier.tasks.size()); + } + + Loggers.RAFT.info("finish to load data from disk,cost: " + (System.currentTimeMillis() - start) + " ms."); + + GlobalExecutor.register(new MasterElection()); + GlobalExecutor.register1(new HeartBeat()); + GlobalExecutor.register(new AddressServerUpdater(), GlobalExecutor.ADDRESS_SERVER_UPDATE_INTVERAL_MS); + + if (peers.size() > 0) { + if (lock.tryLock(INIT_LOCK_TIME_SECONDS, TimeUnit.SECONDS)) { + initialized = true; + lock.unlock(); + } + } else { + throw new Exception("peers is empty."); + } + + Loggers.RAFT.info("timer started: leader timeout ms: " + GlobalExecutor.LEADER_TIMEOUT_MS + + "; heart-beat timeout ms: " + GlobalExecutor.HEARTBEAT_INTVERAL_MS); + } + + public static List getListeners() { + return listeners; + } + + /** + * will return success once local writes success instead of the majority, + * therefore is unsafe + * + * @param key + * @param value + * @throws Exception + */ + public static void unsafePublish(String key, String value) throws Exception { + OPERATE_LOCK.lock(); + + try { + if (!RaftCore.isLeader()) { + JSONObject params = new JSONObject(); + params.put("key", key); + params.put("value", value); + + Map parameters = new HashMap<>(1); + parameters.put("key", key); + RaftProxy.proxyPostLarge(API_UNSF_PUB, params.toJSONString(), parameters); + + if (!RaftCore.isLeader()) { + throw new IllegalStateException("I'm not leader, can not handle update/delete operation"); + } + } + + Datum datum = new Datum(); + datum.key = key; + datum.value = value; + datum.timestamp = System.currentTimeMillis(); + + RaftPeer local = peers.local(); + + JSONObject packet = new JSONObject(); + packet.put("datum", datum); + packet.put("source", local); + + onPublish(packet); + } finally { + OPERATE_LOCK.unlock(); + } + } + + public static void signalPublish(String key, String value) throws Exception { + if (!RaftCore.isLeader()) { + JSONObject params = new JSONObject(); + params.put("key", key); + params.put("value", value); + Map parameters = new HashMap<>(1); + parameters.put("key", key); + + RaftProxy.proxyPostLarge(API_PUB, params.toJSONString(), parameters); + + return; + } + + if (!RaftCore.isLeader()) { + throw new IllegalStateException("I'm not leader, can not handle update/delete operation"); + } + + OPERATE_LOCK.lock(); + + try { + long start = System.currentTimeMillis(); + final Datum datum = new Datum(); + datum.key = key; + datum.value = value; + datum.timestamp = System.currentTimeMillis(); + + JSONObject json = new JSONObject(); + json.put("datum", datum); + json.put("source", peers.local()); + + onPublish(json); + + final String content = JSON.toJSONString(json); + + final CountDownLatch latch = new CountDownLatch(peers.majorityCount()); + for (final String server : peers.allServersIncludeMyself()) { + if (isLeader(server)) { + latch.countDown(); + continue; + } + final String url = buildURL(server, API_ON_PUB); + HttpClient.asyncHttpPostLarge(url, Arrays.asList("key=" + key), content, new AsyncCompletionHandler() { + @Override + public Integer onCompleted(Response response) throws Exception { + if (response.getStatusCode() != HttpURLConnection.HTTP_OK) { + Loggers.RAFT.warn("RAFT", "failed to publish data to peer, datumId=" + datum.key + ", peer=" + server + ", http code=" + response.getStatusCode()); + return 1; + } + latch.countDown(); + return 0; + } + + @Override + public STATE onContentWriteCompleted() { + return STATE.CONTINUE; + } + }); + + } + + // only majority servers return success can we consider this update success + if (!latch.await(UtilsAndCommons.MAX_PUBLISH_WAIT_TIME_MILLIS, TimeUnit.MILLISECONDS)) { + Loggers.RAFT.info("data publish failed, caused failed to notify majority, key=" + key); + throw new IllegalStateException("data publish failed, caused failed to notify majority, key=" + key); + } + + long end = System.currentTimeMillis(); + Loggers.RAFT.info("signalPublish cost " + (end - start) + " ms" + " : " + key); + } finally { + OPERATE_LOCK.unlock(); + } + } + + public static void signalDelete(final String key) throws Exception { + OPERATE_LOCK.lock(); + try { + + if (!isLeader()) { + Map params = new HashMap<>(1); + params.put("key", key); + + RaftProxy.proxyGET(API_DEL, params); + return; + } + + if (!RaftCore.isLeader()) { + throw new IllegalStateException("I'm not leader, can not handle update/delete operation"); + } + + JSONObject json = new JSONObject(); + json.put("key", key); + json.put("source", peers.local()); + + final CountDownLatch latch = new CountDownLatch(peers.majorityCount()); + for (final String server : peers.allServersIncludeMyself()) { + String url = buildURL(server, API_ON_DEL); + HttpClient.asyncHttpPostLarge(url, null, JSON.toJSONString(json) + , new AsyncCompletionHandler() { + @Override + public Integer onCompleted(Response response) throws Exception { + if (response.getStatusCode() != HttpURLConnection.HTTP_OK) { + Loggers.RAFT.warn("RAFT", "failed to delete data from peer, datumId=" + key + ", peer=" + server + ", http code=" + response.getStatusCode()); + return 1; + } + + latch.countDown(); + + RaftPeer local = peers.local(); + + local.resetLeaderDue(); + + return 0; + } + }); + } + + // only majority servers return success can we consider this update success + if (!latch.await(UtilsAndCommons.MAX_PUBLISH_WAIT_TIME_MILLIS, TimeUnit.MILLISECONDS)) { + Loggers.RAFT.info("data delete failed, key=" + key); + + throw new IllegalStateException("data delete failed, caused failed to notify majority, key=" + key); + } + } finally { + OPERATE_LOCK.unlock(); + } + } + + public static void onPublish(JSONObject params) throws Exception { + RaftPeer source = new RaftPeer(); + source.ip = params.getJSONObject("source").getString("ip"); + source.state = RaftPeer.State.valueOf(params.getJSONObject("source").getString("state")); + source.term.set(params.getJSONObject("source").getLongValue("term")); + source.heartbeatDueMs = params.getJSONObject("source").getLongValue("heartbeatDueMs"); + source.leaderDueMs = params.getJSONObject("source").getLongValue("leaderDueMs"); + source.voteFor = params.getJSONObject("source").getString("voteFor"); + RaftPeer local = peers.local(); + Datum datum = params.getObject("datum", Datum.class); + if (StringUtils.isBlank(datum.value)) { + Loggers.RAFT.warn("received empty datum"); + throw new IllegalStateException("received empty datum"); + } + + if (!peers.isLeader(source.ip)) { + Loggers.RAFT.warn("peer(" + JSON.toJSONString(source) + ") tried to publish " + + "data but wasn't leader, leader: " + JSON.toJSONString(getLeader())); + throw new IllegalStateException("peer(" + source.ip + ") tried to publish " + + "data but wasn't leader"); + } + + if (source.term.get() < local.term.get()) { + Loggers.RAFT.warn("out of date publish, pub-term: " + + JSON.toJSONString(source) + ", cur-term: " + JSON.toJSONString(local)); + throw new IllegalStateException("out of date publish, pub-term:" + + source.term.get() + ", cur-term: " + local.term.get()); + } + + local.resetLeaderDue(); + + // do apply + RaftStore.write(datum); + RaftCore.datums.put(datum.key, datum); + + if (isLeader()) { + local.term.addAndGet(PUBLISH_TERM_INCREASE_COUNT); + } else { + if (local.term.get() + PUBLISH_TERM_INCREASE_COUNT > source.term.get()) { + //set leader term: + getLeader().term.set(source.term.get()); + local.term.set(getLeader().term.get()); + } else { + local.term.addAndGet(PUBLISH_TERM_INCREASE_COUNT); + } + } + + RaftStore.updateTerm(local.term.get()); + + notifier.addTask(datum, Notifier.ApplyAction.CHANGE); + + Loggers.RAFT.info("data added/updated, key=" + datum.key + ", term: " + local.term); + } + + public static void onDelete(JSONObject params) throws Exception { + + RaftPeer source = new RaftPeer(); + source.ip = params.getJSONObject("source").getString("ip"); + source.state = RaftPeer.State.valueOf(params.getJSONObject("source").getString("state")); + source.term.set(params.getJSONObject("source").getLongValue("term")); + source.heartbeatDueMs = params.getJSONObject("source").getLongValue("heartbeatDueMs"); + source.leaderDueMs = params.getJSONObject("source").getLongValue("leaderDueMs"); + source.voteFor = params.getJSONObject("source").getString("voteFor"); + + RaftPeer local = peers.local(); + + if (!peers.isLeader(source.ip)) { + Loggers.RAFT.warn("peer(" + JSON.toJSONString(source) + ") tried to publish " + + "data but wasn't leader, leader: " + JSON.toJSONString(getLeader())); + throw new IllegalStateException("peer(" + source.ip + ") tried to publish data but wasn't leader"); + } + + if (source.term.get() < local.term.get()) { + Loggers.RAFT.warn("out of date publish, pub-term: " + + JSON.toJSONString(source) + ", cur-term: " + JSON.toJSONString(local)); + throw new IllegalStateException("out of date publish, pub-term:" + + source.term + ", cur-term: " + local.term); + } + + local.resetLeaderDue(); + + // do apply + String key = params.getString("key"); + deleteDatum(key); + + if (local.term.get() + PUBLISH_TERM_INCREASE_COUNT > source.term.get()) { + //set leader term: + getLeader().term.set(source.term.get()); + local.term.set(getLeader().term.get()); + } else { + local.term.addAndGet(PUBLISH_TERM_INCREASE_COUNT); + } + + RaftStore.updateTerm(local.term.get()); + + } + + public static class MasterElection implements Runnable { + @Override + public void run() { + try { + RaftPeer local = peers.local(); + local.leaderDueMs -= GlobalExecutor.TICK_PERIOD_MS; + if (local.leaderDueMs > 0) { + return; + } + + // reset timeout + local.resetLeaderDue(); + local.resetHeartbeatDue(); + + sendVote(); + } catch (Exception e) { + Loggers.RAFT.warn("RAFT", "error while master election", e); + } + + } + + public static void sendVote() { + if (!initialized) { + // not ready yet + return; + } + + RaftPeer local = peers.get(NetUtils.localIP()); + Loggers.RAFT.info("leader timeout, start voting,leader: " + JSON.toJSONString(getLeader()) + ", term: " + local.term); + + peers.reset(); + + local.term.incrementAndGet(); + local.voteFor = local.ip; + local.state = RaftPeer.State.CANDIDATE; + + Map params = new HashMap(1); + params.put("vote", JSON.toJSONString(local)); + for (final String server : peers.allServersWithoutMySelf()) { + final String url = buildURL(server, API_VOTE); + try { + HttpClient.asyncHttpPost(url, null, params, new AsyncCompletionHandler() { + @Override + public Integer onCompleted(Response response) throws Exception { + if (response.getStatusCode() != HttpURLConnection.HTTP_OK) { + Loggers.RAFT.error("VIPSRV-RAFT", "vote failed: " + , response.getResponseBody() + " url:" + url); + + return 1; + } + + RaftPeer peer = JSON.parseObject(response.getResponseBody(), RaftPeer.class); + + Loggers.RAFT.info("received approve from peer: " + JSON.toJSONString(peer)); + + peers.decideLeader(peer); + + return 0; + } + }); + } catch (Exception e) { + Loggers.RAFT.warn("error while sending vote to server:" + server); + } + } + } + + public static RaftPeer receivedVote(RaftPeer remote) { + if (!peers.contains(remote)) { + throw new IllegalStateException("can not find peer: " + remote.ip); + } + + if (!initialized) { + throw new IllegalStateException("not ready yet"); + } + + RaftPeer local = peers.get(NetUtils.localIP()); + if (remote.term.get() <= local.term.get()) { + String msg = "received illegitimate vote" + + ", voter-term:" + remote.term + ", votee-term:" + local.term; + + Loggers.RAFT.info(msg); + if (StringUtils.isEmpty(local.voteFor)) { + local.voteFor = local.ip; + } + + return local; + } + + local.resetLeaderDue(); + + local.state = RaftPeer.State.FOLLOWER; + local.voteFor = remote.ip; + local.term.set(remote.term.get()); + + Loggers.RAFT.info("vote " + remote.ip + " as leader, term:" + remote.term); + + return local; + } + } + + public static class HeartBeat implements Runnable { + @Override + public void run() { + try { + RaftPeer local = peers.local(); + local.heartbeatDueMs -= GlobalExecutor.TICK_PERIOD_MS; + if (local.heartbeatDueMs > 0) { + return; + } + + local.resetHeartbeatDue(); + + sendBeat(); + } catch (Exception e) { + Loggers.RAFT.warn("RAFT", "error while sending beat", e); + } + + } + + public static void sendBeat() throws IOException, InterruptedException { + RaftPeer local = peers.local(); + if (local.state != RaftPeer.State.LEADER && !UtilsAndCommons.STANDALONE_MODE) { + return; + } + + Loggers.RAFT.info("RAFT", "send beat with " + datums.size() + " keys."); + + local.resetLeaderDue(); + + // build data + JSONObject packet = new JSONObject(); + packet.put("peer", local); + + JSONArray array = new JSONArray(); + + if (Switch.isSendBeatOnly()) { + Loggers.RAFT.info("SEND-BEAT-ONLY", String.valueOf(Switch.isSendBeatOnly())); + } + + if (!Switch.isSendBeatOnly()) { + for (Datum datum : datums.values()) { + + JSONObject element = new JSONObject(); + String key; + + if (datum.key.startsWith(UtilsAndCommons.DOMAINS_DATA_ID)) { + key = (datum.key).split(UtilsAndCommons.DOMAINS_DATA_ID)[1]; + element.put("key", UtilsAndCommons.RAFT_DOM_PRE + key); + } else if (datum.key.startsWith(UtilsAndCommons.IPADDRESS_DATA_ID_PRE)) { + key = (datum.key).split(UtilsAndCommons.IPADDRESS_DATA_ID_PRE)[1]; + element.put("key", UtilsAndCommons.RAFT_IPLIST_PRE + key); + } else if (datum.key.startsWith(UtilsAndCommons.TAG_DOMAINS_DATA_ID)) { + key = (datum.key).split(UtilsAndCommons.TAG_DOMAINS_DATA_ID)[1]; + element.put("key", UtilsAndCommons.RAFT_TAG_DOM_PRE + key); + } else if (datum.key.startsWith(UtilsAndCommons.NODE_TAG_IP_PRE)) { + key = (datum.key).split(UtilsAndCommons.NODE_TAG_IP_PRE)[1]; + element.put("key", UtilsAndCommons.RAFT_TAG_IPLIST_PRE + key); + } + element.put("timestamp", datum.timestamp); + + array.add(element); + } + } else { + Loggers.RAFT.info("RAFT", "send beat only."); + } + + packet.put("datums", array); + // broadcast + Map params = new HashMap(1); + params.put("beat", JSON.toJSONString(packet)); + + String content = JSON.toJSONString(params); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + GZIPOutputStream gzip = new GZIPOutputStream(out); + gzip.write(content.getBytes("UTF-8")); + gzip.close(); + + byte[] compressedBytes = out.toByteArray(); + String compressedContent = new String(compressedBytes, "UTF-8"); + Loggers.RAFT.info("raw beat data size: " + content.length() + ", size of compressed data: " + compressedContent.length()); + + final CountDownLatch latch = new CountDownLatch(peers.majorityCount() - 1); + + for (final String server : peers.allServersWithoutMySelf()) { + try { + final String url = buildURL(server, API_BEAT); + Loggers.RAFT.info("send beat to server " + server); + HttpClient.asyncHttpPostLarge(url, null, compressedBytes, new AsyncCompletionHandler() { + @Override + public Integer onCompleted(Response response) throws Exception { + if (response.getStatusCode() != HttpURLConnection.HTTP_OK) { + Loggers.RAFT.error("VIPSRV-RAFT", "beat failed: " + response.getResponseBody() + ", peer: " + server); + return 1; + } + + latch.countDown(); + + peers.update(JSON.parseObject(response.getResponseBody(), RaftPeer.class)); + Loggers.RAFT.info("receive beat response from: " + url); + return 0; + } + + @Override + public void onThrowable(Throwable t) { + Loggers.RAFT.error("VIPSRV-RAFT", "error while sending heart-beat to peer: " + server, t); + } + }); + } catch (Exception e) { + Loggers.RAFT.error("VIPSRV-RAFT", "error while sending heart-beat to peer: " + server, e); + } + } + + } + + public static RaftPeer receivedBeat(JSONObject beat) throws Exception { + final RaftPeer local = peers.local(); + final RaftPeer remote = new RaftPeer(); + remote.ip = beat.getJSONObject("peer").getString("ip"); + remote.state = RaftPeer.State.valueOf(beat.getJSONObject("peer").getString("state")); + remote.term.set(beat.getJSONObject("peer").getLongValue("term")); + remote.heartbeatDueMs = beat.getJSONObject("peer").getLongValue("heartbeatDueMs"); + remote.leaderDueMs = beat.getJSONObject("peer").getLongValue("leaderDueMs"); + remote.voteFor = beat.getJSONObject("peer").getString("voteFor"); + + if (remote.state != RaftPeer.State.LEADER) { + Loggers.RAFT.info("RAFT", "invalid state from master, state=" + remote.state + ", remote peer: " + JSON.toJSONString(remote)); + throw new IllegalArgumentException("invalid state from master, state=" + remote.state); + } + + if (local.term.get() > remote.term.get()) { + Loggers.RAFT.info("RAFT", "out of date beat, beat-from-term: " + remote.term.get() + + ", beat-to-term: " + local.term.get() + ", remote peer: " + JSON.toJSONString(remote) + ", and leaderDueMs: " + local.leaderDueMs); + throw new IllegalArgumentException("out of date beat, beat-from-term: " + remote.term.get() + + ", beat-to-term: " + local.term.get()); + } + + if (local.state != RaftPeer.State.FOLLOWER) { + + Loggers.RAFT.info("RAFT", "make remote as leader " + ", remote peer: " + JSON.toJSONString(remote)); + // mk follower + local.state = RaftPeer.State.FOLLOWER; + local.voteFor = remote.ip; + } + + final JSONArray beatDatums = beat.getJSONArray("datums"); + local.resetLeaderDue(); + local.resetHeartbeatDue(); + + peers.makeLeader(remote); + + Map receivedKeysMap = new HashMap(RaftCore.datums.size()); + + for (Map.Entry entry : RaftCore.datums.entrySet()) { + receivedKeysMap.put(entry.getKey(), 0); + } + + // now check datums + List batch = new ArrayList(); + if (!Switch.isSendBeatOnly()) { + int processedCount = 0; + Loggers.RAFT.info("RAFT", "received beat with " + beatDatums.size() + " keys, RaftCore.datums' size is " + + RaftCore.datums.size() + ", remote server: " + remote.ip + ", term: " + remote.term + ", local term: " + local.term); + for (Object object : beatDatums) { + processedCount = processedCount + 1; + + JSONObject entry = (JSONObject) object; + String key = entry.getString("key"); + final String datumKey; + + if (key.startsWith(UtilsAndCommons.RAFT_DOM_PRE)) { + int index = key.indexOf(UtilsAndCommons.RAFT_DOM_PRE); + datumKey = UtilsAndCommons.DOMAINS_DATA_ID + key.substring(index + UtilsAndCommons.RAFT_DOM_PRE.length()); + } else if (key.startsWith(UtilsAndCommons.RAFT_IPLIST_PRE)) { + int index = key.indexOf(UtilsAndCommons.RAFT_IPLIST_PRE); + datumKey = UtilsAndCommons.IPADDRESS_DATA_ID_PRE + key.substring(index + UtilsAndCommons.RAFT_IPLIST_PRE.length()); + } else if (key.startsWith(UtilsAndCommons.RAFT_TAG_DOM_PRE)) { + int index = key.indexOf(UtilsAndCommons.RAFT_TAG_DOM_PRE); + datumKey = UtilsAndCommons.TAG_DOMAINS_DATA_ID + key.substring(index + UtilsAndCommons.RAFT_TAG_DOM_PRE.length()); + } else { + int index = key.indexOf(UtilsAndCommons.RAFT_TAG_IPLIST_PRE); + datumKey = UtilsAndCommons.NODE_TAG_IP_PRE + key.substring(index + UtilsAndCommons.RAFT_TAG_IPLIST_PRE.length()); + } + + long timestamp = entry.getLong("timestamp"); + + receivedKeysMap.put(datumKey, 1); + + + try { + if (RaftCore.datums.containsKey(datumKey) && RaftCore.datums.get(datumKey).timestamp >= timestamp && processedCount < beatDatums.size()) { + continue; + } + + if (!(RaftCore.datums.containsKey(datumKey) && RaftCore.datums.get(datumKey).timestamp >= timestamp)) { + batch.add(datumKey); + } + + if (batch.size() < 50 && processedCount < beatDatums.size()) { + continue; + } + + String keys = StringUtils.join(batch, ","); + + if (batch.size() <= 0) { + continue; + } + + Loggers.RAFT.info("get datums from leader: " + getLeader().ip + " , batch size is " + batch.size() + ", processedCount is " + processedCount + + ", datums' size is " + beatDatums.size() + ", RaftCore.datums' size is " + RaftCore.datums.size()); + + // update datum entry + String url = buildURL(remote.ip, API_GET) + "?keys=" + keys; + HttpClient.asyncHttpGet(url, null, null, new AsyncCompletionHandler() { + @Override + public Integer onCompleted(Response response) throws Exception { + if (response.getStatusCode() != HttpURLConnection.HTTP_OK) { + return 1; + } + + List datumList = JSON.parseObject(response.getResponseBody(), new TypeReference>() { + }); + + for (Datum datum : datumList) { + OPERATE_LOCK.lock(); + try { + + Datum oldDatum = RaftCore.getDatum(datum.key); + + if (oldDatum != null && datum.timestamp <= oldDatum.timestamp) { + Loggers.RAFT.info("VIPSRV-RAFT", "timestamp is smaller than that of mine, key: " + datum.key + + ",remote: " + datum.timestamp + ", local: " + oldDatum.timestamp); + continue; + } + + RaftStore.write(datum); + RaftCore.datums.put(datum.key, datum); + local.resetLeaderDue(); + + if (local.term.get() + 100 > remote.term.get()) { + getLeader().term.set(remote.term.get()); + local.term.set(getLeader().term.get()); + } else { + local.term.addAndGet(100); + } + + RaftStore.updateTerm(local.term.get()); + + Loggers.RAFT.info("data updated" + ", key=" + datum.key + + ", timestamp=" + datum.timestamp + ",from " + JSON.toJSONString(remote) + ", local term: " + local.term); + + notifier.addTask(datum, Notifier.ApplyAction.CHANGE); + } catch (Throwable e) { + Loggers.RAFT.error("RAFT-BEAT", "failed to sync datum from leader, key: " + datum.key, e); + } finally { + OPERATE_LOCK.unlock(); + } + } + TimeUnit.MILLISECONDS.sleep(200); + return 0; + } + }); + + batch.clear(); + + } catch (Exception e) { + Loggers.RAFT.error("VIPSRV-RAFT", "failed to handle beat entry, key=" + datumKey); + } + + } + + List deadKeys = new ArrayList(); + for (Map.Entry entry : receivedKeysMap.entrySet()) { + if (entry.getValue() == 0) { + deadKeys.add(entry.getKey()); + } + } + + for (String deadKey : deadKeys) { + try { + deleteDatum(deadKey); + } catch (Exception e) { + Loggers.RAFT.error("VIPSRV-RAFT", "failed to remove entry, key=" + deadKey, e); + } + } + + } + + + return local; + } + } + + public static class AddressServerUpdater implements Runnable { + @Override + public void run() { + try { + List servers = NamingProxy.getServers(); + List peerList = new ArrayList(peers.allPeers()); + List oldServers = new ArrayList(); + + if (CollectionUtils.isEmpty(servers)) { + Loggers.RAFT.warn("get empty server list from address server,ignore it."); + return; + } + + for (RaftPeer peer : peerList) { + oldServers.add(peer.ip); + } + + List newServers = (List) CollectionUtils.subtract(servers, oldServers); + if (!CollectionUtils.isEmpty(newServers)) { + peers.add(newServers); + Loggers.RAFT.info("RAFT", "server list is updated, new (" + newServers.size() + ") servers: " + newServers); + } + + List deadServers = (List) CollectionUtils.subtract(oldServers, servers); + if (!CollectionUtils.isEmpty(deadServers)) { + peers.remove(deadServers); + Loggers.RAFT.info("RAFT", "server list is updated, dead (" + deadServers.size() + ") servers: " + deadServers); + } + } catch (Exception e) { + Loggers.RAFT.info("RAFT", "error while updating server list.", e); + } + } + } + + public static void listen(RaftListener listener) { + if (listeners.contains(listener)) { + return; + } + + listeners.add(listener); + + for (RaftListener listener1 : listeners) { + if (listener1 instanceof VirtualClusterDomain) { + Loggers.RAFT.debug("listener in listeners: " + ((VirtualClusterDomain) listener1).getName()); + } + } + + if (listeners.contains(listener)) { + if (listener instanceof VirtualClusterDomain) { + Loggers.RAFT.info("add listener: " + ((VirtualClusterDomain) listener).getName()); + } else { + Loggers.RAFT.info("add listener for switch or domain meta. "); + } + } else { + Loggers.RAFT.error("VIPSRV-RAFT", "faild to add listener: " + JSON.toJSONString(listener)); + } + // if data present, notify immediately + for (Datum datum : datums.values()) { + if (!listener.interests(datum.key)) { + continue; + } + + try { + listener.onChange(datum.key, datum.value); + } catch (Exception e) { + Loggers.RAFT.error("VIPSRV-RAFT", "failed to notify listener", e); + } + } + } + + public static void unlisten(String key) { + for (RaftListener listener : listeners) { + if (listener.matchUnlistenKey(key)) { + listeners.remove(listener); + } + } + } + + public static void setTerm(long term) { + RaftCore.peers.setTerm(term); + } + + public static long getTerm() { + return RaftCore.peers.getTerm(); + } + + public static boolean isInitialized() { + return initialized; + } + + public static boolean isLeader(String ip) { + return peers.isLeader(ip); + } + + public static boolean isLeader() { + return peers.isLeader(NetUtils.localIP()); + } + + public static String buildURL(String ip, String api) { + if (!ip.contains(UtilsAndCommons.CLUSTER_CONF_IP_SPLITER)) { + ip = ip + UtilsAndCommons.CLUSTER_CONF_IP_SPLITER + RunningConfig.getServerPort(); + } + return "http://" + ip + RunningConfig.getContextPath() + api; + } + + public static Datum getDatum(String key) { + return datums.get(key); + } + + public static RaftPeer getLeader() { + return peers.getLeader(); + } + + public static List getPeers() { + return new ArrayList(peers.allPeers()); + } + + public static PeerSet getPeerSet() { + return peers; + } + + public static void setPeerSet(PeerSet peerSet) { + peers = peerSet; + } + + public static int datumSize() { + return datums.size(); + } + + public static void addDatum(Datum datum) { + datums.put(datum.key, datum); + notifier.addTask(datum, Notifier.ApplyAction.CHANGE); + } + + private static void deleteDatum(String key) { + Datum deleted = datums.remove(key); + if (deleted != null) { + RaftStore.delete(deleted); + notifier.addTask(deleted, Notifier.ApplyAction.DELETE); + + Loggers.RAFT.info("datum deleted, key=" + key); + } + } + + public static class Notifier implements Runnable { + + private BlockingQueue tasks = new LinkedBlockingQueue(1024 * 1024); + + public void addTask(Datum datum, ApplyAction action) { + tasks.add(Pair.with(datum, action)); + } + + @Override + public void run() { + Loggers.RAFT.info("raft notifier started"); + + while (true) { + try { + + Pair pair = tasks.take(); + + if (pair == null) { + continue; + } + + Datum datum = (Datum) pair.getValue0(); + ApplyAction action = (ApplyAction) pair.getValue1(); + int count = 0; + for (RaftListener listener : listeners) { + + if (listener instanceof VirtualClusterDomain) { + Loggers.RAFT.debug("listener: " + ((VirtualClusterDomain) listener).getName()); + } + + if (!listener.interests(datum.key)) { + continue; + } + + count++; + + try { + if (action == ApplyAction.CHANGE) { + listener.onChange(datum.key, datum.value); + continue; + } + + if (action == ApplyAction.DELETE) { + listener.onDelete(datum.key, datum.value); + continue; + } + } catch (Throwable e) { + Loggers.RAFT.error("VIPSRV-RAFT", "error while notifying listener of key: " + + datum.key, e); + } + } + + Loggers.RAFT.debug("VIPSRV-RAFT", "datum change notified" + + ", key: " + datum.key + "; listener count: " + count); + } catch (Throwable e) { + Loggers.RAFT.error("VIPSRV-RAFT", "Error while handling notifying task", e); + } + } + } + + public enum ApplyAction { + /** + * Data changed + */ + CHANGE, + /** + * Data deleted + */ + DELETE + } + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/raft/RaftListener.java b/naming/src/main/java/com/alibaba/nacos/naming/raft/RaftListener.java new file mode 100644 index 00000000000..c3ce2b9c5c4 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/raft/RaftListener.java @@ -0,0 +1,56 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.raft; + +/** + * @author nacos + */ +public interface RaftListener { + + /** + * Determine if the listener was registered with this key + * + * @param key candidate key + * @return true if the listener was registered with this key + */ + boolean interests(String key); + + /** + * Determine if the listener is to be removed by matching the 'key' + * + * @param key key to match + * @return true if match success + */ + boolean matchUnlistenKey(String key); + + /** + * Action to do if data of target key has changed + * + * @param key target key + * @param value data of the key + * @throws Exception + */ + void onChange(String key, String value) throws Exception; + + /** + * Action to do if data of target key has been removed + * + * @param key target key + * @param value data of the key + * @throws Exception + */ + void onDelete(String key, String value) throws Exception; +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/raft/RaftPeer.java b/naming/src/main/java/com/alibaba/nacos/naming/raft/RaftPeer.java new file mode 100644 index 00000000000..b9156e786f8 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/raft/RaftPeer.java @@ -0,0 +1,87 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.raft; + +import org.apache.commons.lang3.RandomUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; + +/** + * @author nacos + */ +public class RaftPeer { + + public String ip; + + public String voteFor; + + public AtomicLong term = new AtomicLong(0L); + + public volatile long leaderDueMs = RandomUtils.nextLong(0, GlobalExecutor.LEADER_TIMEOUT_MS); + + public volatile long heartbeatDueMs = RandomUtils.nextLong(0, GlobalExecutor.HEARTBEAT_INTVERAL_MS); + + public State state = State.FOLLOWER; + + public void resetLeaderDue() { + leaderDueMs = GlobalExecutor.LEADER_TIMEOUT_MS + RandomUtils.nextLong(0, GlobalExecutor.RAMDOM_MS); + } + + public void resetHeartbeatDue() { + heartbeatDueMs = GlobalExecutor.HEARTBEAT_INTVERAL_MS; + } + + public enum State { + /** + * Leader of the cluster, only one leader stands in a cluster + */ + LEADER, + /** + * Follower of the cluster, report to and copy from leader + */ + FOLLOWER, + /** + * Candidate leader to be elected + */ + CANDIDATE + } + + @Override + public int hashCode() { + return Objects.hash(ip); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + + if (!(obj instanceof RaftPeer)) { + return false; + } + + RaftPeer other = (RaftPeer) obj; + + if (StringUtils.equals(ip, other.ip)) { + return true; + } + + return false; + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/raft/RaftProxy.java b/naming/src/main/java/com/alibaba/nacos/naming/raft/RaftProxy.java new file mode 100644 index 00000000000..2117debb4a6 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/raft/RaftProxy.java @@ -0,0 +1,72 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.raft; + +import com.alibaba.nacos.naming.boot.RunningConfig; +import com.alibaba.nacos.naming.misc.HttpClient; +import com.alibaba.nacos.naming.misc.UtilsAndCommons; + +import java.net.HttpURLConnection; +import java.util.Map; + +/** + * @author nacos + */ +public class RaftProxy { + public static void proxyGET(String api, Map params) throws Exception { + if (RaftCore.isLeader()) { + throw new IllegalStateException("I'm leader, no need to do proxy"); + } + + if (RaftCore.getLeader() == null) { + throw new IllegalStateException("No leader at present"); + } + + // do proxy + String server = RaftCore.getLeader().ip; + if (!server.contains(UtilsAndCommons.CLUSTER_CONF_IP_SPLITER)) { + server = server + UtilsAndCommons.CLUSTER_CONF_IP_SPLITER + RunningConfig.getServerPort(); + } + String url = "http://" + server + RunningConfig.getContextPath() + api; + + HttpClient.HttpResult result = HttpClient.httpGet(url, null, params); + if (result.code != HttpURLConnection.HTTP_OK) { + throw new IllegalStateException("leader failed, caused by: " + result.content); + } + } + + public static void proxyPostLarge(String api, String content, Map headers) throws Exception { + if (RaftCore.isLeader()) { + throw new IllegalStateException("I'm leader, no need to do proxy"); + } + + if (RaftCore.getLeader() == null) { + throw new IllegalStateException("No leader at present"); + } + + // do proxy + String server = RaftCore.getLeader().ip; + if (!server.contains(UtilsAndCommons.CLUSTER_CONF_IP_SPLITER)) { + server = server + UtilsAndCommons.CLUSTER_CONF_IP_SPLITER + RunningConfig.getServerPort(); + } + String url = "http://" + server + RunningConfig.getContextPath() + api; + + HttpClient.HttpResult result = HttpClient.httpPostLarge(url, headers, content); + if (result.code != HttpURLConnection.HTTP_OK) { + throw new IllegalStateException("leader failed, caused by: " + result.content); + } + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/raft/RaftStore.java b/naming/src/main/java/com/alibaba/nacos/naming/raft/RaftStore.java new file mode 100644 index 00000000000..a50c0cfa38c --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/raft/RaftStore.java @@ -0,0 +1,193 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.raft; + +import com.alibaba.nacos.naming.misc.Loggers; +import com.alibaba.fastjson.JSON; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.Properties; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; + +/** + * @author nacos + */ +public class RaftStore { + + private static String BASE_DIR = System.getProperty("user.home") + File.separator + "nacos" + File.separator + "raft"; + + private static String META_FILE_NAME; + + private static String CACHE_DIR; + + private static Properties meta = new Properties(); + + static { + + String nacosHome = System.getProperty("nacos.home"); + if (StringUtils.isNotBlank(nacosHome)) { + BASE_DIR = nacosHome + File.separator + "data" + File.separator + "naming"; + } + + META_FILE_NAME = BASE_DIR + File.separator + "meta.properties"; + CACHE_DIR = BASE_DIR + File.separator + "data"; + } + + public synchronized static void load() throws Exception{ + long start = System.currentTimeMillis(); + // load data + for (File cache : listCaches()) { + if (!cache.isFile()) { + Loggers.RAFT.warn("warning: encountered directory in cache dir: " + cache.getAbsolutePath()); + } + + ByteBuffer buffer; + FileChannel fc = null; + try { + fc = new FileInputStream(cache).getChannel(); + buffer = ByteBuffer.allocate((int) cache.length()); + fc.read(buffer); + + String json = new String(buffer.array(), "UTF-8"); + if (StringUtils.isBlank(json)) { + continue; + } + + Datum datum = JSON.parseObject(json, Datum.class); + RaftCore.addDatum(datum); + } catch (Exception e) { + Loggers.RAFT.warn("waning: failed to deserialize key: " + cache.getName()); + throw e; + } finally { + if (fc != null) { + fc.close(); + } + } + } + + // load meta + File meta = new File(META_FILE_NAME); + if (!meta.exists() && !meta.getParentFile().mkdirs() && !meta.createNewFile()) { + throw new IllegalStateException("failed to create meta file: " + meta.getAbsolutePath()); + } + + try (FileInputStream inStream = new FileInputStream(meta)) { + RaftStore.meta.load(inStream); + RaftCore.setTerm(NumberUtils.toLong(RaftStore.meta.getProperty("term"), 0L)); + } + + Loggers.RAFT.info("finish loading all datums, size: " + RaftCore.datumSize() + " cost " + (System.currentTimeMillis() - start) + "ms."); + } + + public synchronized static void load(String key) throws Exception{ + long start = System.currentTimeMillis(); + // load data + for (File cache : listCaches()) { + if (!cache.isFile()) { + Loggers.RAFT.warn("warning: encountered directory in cache dir: " + cache.getAbsolutePath()); + } + + if (!StringUtils.equals(cache.getName(), key)) { + continue; + } + + ByteBuffer buffer; + FileChannel fc = null; + try { + fc = new FileInputStream(cache).getChannel(); + buffer = ByteBuffer.allocate((int) cache.length()); + fc.read(buffer); + + String json = new String(buffer.array(), "UTF-8"); + if (StringUtils.isBlank(json)) { + continue; + } + + Datum datum = JSON.parseObject(json, Datum.class); + RaftCore.addDatum(datum); + } catch (Exception e) { + Loggers.RAFT.warn("waning: failed to deserialize key: " + cache.getName()); + throw e; + } finally { + if (fc != null) { + fc.close(); + } + } + } + + Loggers.RAFT.info("finish loading datum, key: " + key + " cost " + (System.currentTimeMillis() - start) + "ms."); + } + + public synchronized static void write(final Datum datum) throws Exception { + File cacheFile = new File(CACHE_DIR + File.separator + datum.key); + if (!cacheFile.exists() && !cacheFile.getParentFile().mkdirs() && !cacheFile.createNewFile()) { + throw new IllegalStateException("can not make cache file: " + cacheFile.getName()); + } + + FileChannel fc = null; + ByteBuffer data = ByteBuffer.wrap(JSON.toJSONString(datum).getBytes("UTF-8")); + + try { + fc = new FileOutputStream(cacheFile, false).getChannel(); + fc.write(data, data.position()); + fc.force(true); + } finally { + if (fc != null) { + fc.close(); + } + } + + } + + private static File[] listCaches() throws Exception { + File cacheDir = new File(CACHE_DIR); + if (!cacheDir.exists() && !cacheDir.mkdirs()) { + throw new IllegalStateException("cloud not make out directory: " + cacheDir.getName()); + } + + return cacheDir.listFiles(); + } + + public static void delete(Datum datum) { + File cacheFile = new File(CACHE_DIR + File.separator + datum.key); + if (!cacheFile.delete()) { + Loggers.RAFT.error("RAFT-DELETE", "failed to delete datum: " + datum.key + ", value: " + datum.value); + throw new IllegalStateException("failed to delete datum: " + datum.key); + } + } + + public static void updateTerm(long term) throws Exception { + File file = new File(META_FILE_NAME); + if (!file.exists() && !file.getParentFile().mkdirs() && !file.createNewFile()) { + throw new IllegalStateException("failed to create meta file"); + } + + try (FileOutputStream outStream = new FileOutputStream(file)) { + // write meta + meta.setProperty("term", String.valueOf(term)); + meta.store(outStream, null); + } + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/web/ApiCommands.java b/naming/src/main/java/com/alibaba/nacos/naming/web/ApiCommands.java new file mode 100644 index 00000000000..b089c63b2e1 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/web/ApiCommands.java @@ -0,0 +1,2452 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.web; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.TypeReference; +import com.alibaba.nacos.common.util.IoUtils; +import com.alibaba.nacos.common.util.Md5Utils; +import com.alibaba.nacos.common.util.SystemUtil; +import com.alibaba.nacos.naming.boot.RunningConfig; +import com.alibaba.nacos.naming.core.*; +import com.alibaba.nacos.naming.exception.NacosException; +import com.alibaba.nacos.naming.healthcheck.*; +import com.alibaba.nacos.naming.misc.*; +import com.alibaba.nacos.naming.push.ClientInfo; +import com.alibaba.nacos.naming.push.DataSource; +import com.alibaba.nacos.naming.push.PushService; +import com.alibaba.nacos.naming.raft.Datum; +import com.alibaba.nacos.naming.raft.RaftCore; +import com.alibaba.nacos.naming.raft.RaftPeer; +import com.alibaba.nacos.naming.raft.RaftProxy; +import com.ning.http.client.AsyncCompletionHandler; +import com.ning.http.client.Response; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.apache.catalina.util.ParameterMap; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.codehaus.jackson.util.VersionUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.io.*; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.security.AccessControlException; +import java.security.InvalidParameterException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Old API entry + * + * @author nacos + */ +@RestController +@RequestMapping(UtilsAndCommons.NACOS_NAMING_CONTEXT + "/api") +public class ApiCommands { + + @Autowired + protected DomainsManager domainsManager; + + private ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setName("com.alibaba.nacos.naming.setWeights4AllIPs.thread"); + t.setDaemon(true); + return t; + } + }); + + private DataSource pushDataSource = new DataSource() { + @Override + public String getData(PushService.PushClient client) { + + Map params = new HashMap(10); + params.put("dom", new String[]{client.getDom()}); + params.put("clusters", new String[]{client.getClusters()}); + + // set udp port to 0, otherwise will cause recursion + params.put("udpPort", new String[]{"0"}); + + InetAddress inetAddress = client.getSocketAddr().getAddress(); + params.put("clientIP", new String[]{inetAddress.getHostAddress()}); + params.put("header:Client-Version", new String[]{client.getAgent()}); + + JSONObject result = new JSONObject(); + try { + result = srvIPXT(MockHttpRequest.buildRequest(params)); + } catch (Exception e) { + Loggers.SRV_LOG.warn("PUSH-SERVICE", "dom is not modified"); + } + + // overdrive the cache millis to push mode + result.put("cacheMillis", Switch.getPushCacheMillis(client.getDom())); + + return result.toJSONString(); + } + }; + + + @RequestMapping("/dom") + public JSONObject dom(HttpServletRequest request) throws NacosException { + // SDK before version 2.0,0 use 'name' instead of 'dom' here + String name = BaseServlet.optional(request, "name", StringUtils.EMPTY); + if (StringUtils.isEmpty(name)) { + name = BaseServlet.required(request, "dom"); + } + + Loggers.SRV_LOG.info("DOM", "request dom:" + name); + + Domain dom = domainsManager.getDomain(name); + if (dom == null) { + throw new NacosException(NacosException.NOT_FOUND, "Dom doesn't exist"); + } + + return toPacket(dom); + } + + @RequestMapping("/domCount") + public JSONObject domCount(HttpServletRequest request) { + + JSONObject result = new JSONObject(); + result.put("count", domainsManager.getDomCount()); + + return result; + } + + @RequestMapping("/rt4Dom") + public JSONObject rt4Dom(HttpServletRequest request) { + String dom = BaseServlet.required(request, "dom"); + + VirtualClusterDomain domObj + = (VirtualClusterDomain) domainsManager.getDomain(dom); + if (domObj == null) { + throw new IllegalArgumentException("request dom doesn't exist"); + } + + JSONObject result = new JSONObject(); + + JSONArray clusters = new JSONArray(); + for (Map.Entry entry : domObj.getClusterMap().entrySet()) { + JSONObject packet = new JSONObject(); + HealthCheckTask task = entry.getValue().getHealthCheckTask(); + + packet.put("name", entry.getKey()); + packet.put("checkRTBest", task.getCheckRTBest()); + packet.put("checkRTWorst", task.getCheckRTWorst()); + packet.put("checkRTNormalized", task.getCheckRTNormalized()); + + clusters.add(packet); + } + result.put("clusters", clusters); + + return result; + } + + @RequestMapping("/ip4Dom2") + public JSONObject ip4Dom2(HttpServletRequest request) throws NacosException { + String domName = BaseServlet.required(request, "dom"); + + VirtualClusterDomain dom = (VirtualClusterDomain) domainsManager.getDomain(domName); + + if (dom == null) { + throw new NacosException(NacosException.NOT_FOUND, "dom: " + domName + " not found."); + } + + List ips = dom.allIPs(); + + JSONObject result = new JSONObject(); + JSONArray ipArray = new JSONArray(); + + for (IpAddress ip : ips) { + ipArray.add(ip.toIPAddr() + "_" + ip.isValid() + "_" + ip.getInvalidType()); + } + + result.put("ips", ipArray); + return result; + } + + @RequestMapping("/ip4Dom") + public JSONObject ip4Dom(HttpServletRequest request) throws Exception { + + JSONObject result = new JSONObject(); + try { + String domName = BaseServlet.required(request, "dom"); + String clusters = BaseServlet.optional(request, "clusters", StringUtils.EMPTY); + String agent = BaseServlet.optional(request, "header:Client-Version", StringUtils.EMPTY); + + VirtualClusterDomain dom = (VirtualClusterDomain) domainsManager.getDomain(domName); + + if (dom == null) { + throw new NacosException(NacosException.NOT_FOUND, "dom: " + domName + " not found!"); + } + + List ips = null; + if (StringUtils.isEmpty(clusters)) { + ips = dom.allIPs(); + } else { + ips = dom.allIPs(Arrays.asList(clusters.split(","))); + } + + if (CollectionUtils.isEmpty(ips)) { + result.put("ips", Collections.emptyList()); + return result; + } + + ClientInfo clientInfo = new ClientInfo(agent); + + JSONArray ipArray = new JSONArray(); + for (IpAddress ip : ips) { + JSONObject ipPac = new JSONObject(); + + ipPac.put("ip", ip.getIp()); + ipPac.put("valid", ip.isValid()); + ipPac.put("port", ip.getPort()); + ipPac.put("marked", ip.isMarked()); + ipPac.put("app", ip.getApp()); + + if (clientInfo.version.compareTo(VersionUtil.parseVersion("1.5.0")) >= 0) { + ipPac.put("weight", ip.getWeight()); + } else { + double weight = ip.getWeight(); + if (weight == 0) { + ipPac.put("weight", (int) ip.getWeight()); + } else { + ipPac.put("weight", ip.getWeight() < 1 ? 1 : (int) ip.getWeight()); + } + } + ipPac.put("checkRT", ip.getCheckRT()); + ipPac.put("cluster", ip.getClusterName()); + ipPac.put("invalidType", ip.getInvalidType()); + + ipArray.add(ipPac); + } + + result.put("ips", ipArray); + } catch (Throwable e) { + Loggers.SRV_LOG.warn("VIPSRV-IP4DOM", "failed to call ip4Dom, caused " + e.getMessage()); + throw new IllegalArgumentException(e); + } + + return result; + } + + @RequestMapping("/regDom") + public String regDom(HttpServletRequest request) throws Exception { + + + String dom = BaseServlet.required(request, "dom"); + if (domainsManager.getDomain(dom) != null) { + throw new IllegalArgumentException("specified dom already exists, dom : " + dom); + } + + addOrReplaceDom(request); + + return "ok"; + } + + @RequestMapping("/clientBeat") + public JSONObject clientBeat(HttpServletRequest request) throws Exception { + String beat = BaseServlet.required(request, "beat"); + RsInfo clientBeat = JSON.parseObject(beat, RsInfo.class); + String dom = BaseServlet.required(request, "dom"); + String app; + app = BaseServlet.optional(request, "app", StringUtils.EMPTY); + String clusterName = clientBeat.getCluster(); + + Loggers.TENANT.debug("client-beat", "beat: " + beat); + VirtualClusterDomain virtualClusterDomain = (VirtualClusterDomain) domainsManager.getDomain(dom); + + //if domain does not exist, register it. + if (virtualClusterDomain == null) { + Map stringMap = new HashMap<>(16); + stringMap.put("dom", Arrays.asList(dom).toArray(new String[1])); + stringMap.put("enableClientBeat", Arrays.asList("true").toArray(new String[1])); + stringMap.put("cktype", Arrays.asList("TCP").toArray(new String[1])); + stringMap.put("appName", Arrays.asList(app).toArray(new String[1])); + stringMap.put("clusterName", Arrays.asList(clusterName).toArray(new String[1])); + regDom(MockHttpRequest.buildRequest(stringMap)); + + virtualClusterDomain = (VirtualClusterDomain) domainsManager.getDomain(dom); + String ip = clientBeat.getIp(); + int port = clientBeat.getPort(); + + IpAddress ipAddress = new IpAddress(); + ipAddress.setPort(port); + ipAddress.setIp(ip); + ipAddress.setWeight(1); + ipAddress.setClusterName(clusterName); + + stringMap.put("ipList", Arrays.asList(JSON.toJSONString(Arrays.asList(ipAddress))).toArray(new String[1])); + stringMap.put("json", Arrays.asList("true").toArray(new String[1])); + addIP4Dom(MockHttpRequest.buildRequest(stringMap)); + Loggers.SRV_LOG.warn("dom not found, register it, dom:" + dom); + } + + if (!DistroMapper.responsible(dom)) { + String server = DistroMapper.mapSrv(dom); + Loggers.EVT_LOG.info("I'm not responsible for " + dom + ", proxy it to " + server); + Map proxyParams = new HashMap<>(16); + for (Map.Entry entry : request.getParameterMap().entrySet()) { + String key = entry.getKey(); + String value = entry.getValue()[0]; + proxyParams.put(key, value); + } + + if (!server.contains(UtilsAndCommons.CLUSTER_CONF_IP_SPLITER)) { + server = server + UtilsAndCommons.CLUSTER_CONF_IP_SPLITER + RunningConfig.getServerPort(); + } + + String url = "http://" + server + RunningConfig.getContextPath() + + UtilsAndCommons.NACOS_NAMING_CONTEXT + "/api/clientBeat"; + HttpClient.HttpResult httpResult = HttpClient.httpGet(url, null, proxyParams); + + if (httpResult.code != HttpURLConnection.HTTP_OK) { + throw new IllegalArgumentException("failed to proxy client beat to" + server + ", beat: " + beat); + } + } else { + if (virtualClusterDomain != null) { + virtualClusterDomain.processClientBeat(clientBeat); + } + } + + JSONObject result = new JSONObject(); + + result.put("clientBeatInterval", Switch.getClientBeatInterval()); + + return result; + } + + + private String addOrReplaceDom(HttpServletRequest request) throws Exception { + + String dom = BaseServlet.required(request, "dom"); + String owners = BaseServlet.optional(request, "owners", StringUtils.EMPTY); + String token = BaseServlet.optional(request, "token", Md5Utils.getMD5(dom, "utf-8")); + + float protectThreshold = NumberUtils.toFloat(BaseServlet.optional(request, "protectThreshold", "0.0")); + boolean isUseSpecifiedURL = Boolean.parseBoolean(BaseServlet.optional(request, "isUseSpecifiedURL", "false")); + String envAndSite = BaseServlet.optional(request, "envAndSites", StringUtils.EMPTY); + boolean resetWeight = Boolean.parseBoolean(BaseServlet.optional(request, "resetWeight", "false")); + boolean enableHealthCheck = Boolean.parseBoolean(BaseServlet.optional(request, "enableHealthCheck", "true")); + boolean enable = Boolean.parseBoolean(BaseServlet.optional(request, "enable", "true")); + String disabledSites = BaseServlet.optional(request, "disabledSites", StringUtils.EMPTY); + boolean eanbleClientBeat = Boolean.parseBoolean(BaseServlet.optional(request, "enableClientBeat", "false")); + String clusterName = BaseServlet.optional(request, "clusterName", UtilsAndCommons.DEFAULT_CLUSTER_NAME); + + String serviceMetadataJson = BaseServlet.optional(request, "serviceMetadata", StringUtils.EMPTY); + String clusterMetadataJson = BaseServlet.optional(request, "clusterMetadata", StringUtils.EMPTY); + + Loggers.SRV_LOG.info("RESET-WEIGHT", String.valueOf(resetWeight)); + + VirtualClusterDomain domObj = new VirtualClusterDomain(); + domObj.setName(dom); + domObj.setToken(token); + domObj.setOwners(Arrays.asList(owners.split(","))); + domObj.setProtectThreshold(protectThreshold); + domObj.setUseSpecifiedURL(isUseSpecifiedURL); + domObj.setResetWeight(resetWeight); + domObj.setEnableHealthCheck(enableHealthCheck); + domObj.setEnabled(enable); + domObj.setEnableClientBeat(eanbleClientBeat); + + if (StringUtils.isNotEmpty(serviceMetadataJson)) { + domObj.setMetadata(JSON.parseObject(serviceMetadataJson, new TypeReference>() { + })); + } + + if (StringUtils.isNotEmpty(envAndSite) && StringUtils.isNotEmpty(disabledSites)) { + throw new IllegalArgumentException("envAndSite and disabledSites are not allowed both not empty."); + } + + String clusters = BaseServlet.optional(request, "clusters", StringUtils.EMPTY); + if (!StringUtils.isEmpty(clusters)) { + // new format + List clusterObjs = JSON.parseArray(clusters, Cluster.class); + + for (Cluster cluster : clusterObjs) { + domObj.getClusterMap().put(cluster.getName(), cluster); + } + } else { + // old format, default cluster will be constructed automatically + String cktype = BaseServlet.optional(request, "cktype", "TCP"); + String ipPort4Check = BaseServlet.optional(request, "ipPort4Check", "true"); + String nodegroup = BaseServlet.optional(request, "nodegroup", StringUtils.EMPTY); + + int defIPPort = NumberUtils.toInt(BaseServlet.optional(request, "defIPPort", "-1")); + int defCkport = NumberUtils.toInt(BaseServlet.optional(request, "defCkport", "80")); + + Cluster cluster = new Cluster(); + cluster.setName(clusterName); + + cluster.setLegacySyncConfig(nodegroup); + + cluster.setUseIPPort4Check(Boolean.parseBoolean(ipPort4Check)); + cluster.setDefIPPort(defIPPort); + cluster.setDefCkport(defCkport); + + if (StringUtils.isNotEmpty(clusterMetadataJson)) { + cluster.setMetadata(JSON.parseObject(clusterMetadataJson, new TypeReference>() { + })); + } + + if (AbstractHealthCheckConfig.Tcp.TYPE.equals(cktype)) { + AbstractHealthCheckConfig.Tcp config = new AbstractHealthCheckConfig.Tcp(); + cluster.setHealthChecker(config); + } else if (AbstractHealthCheckConfig.Http.TYPE.equals(cktype)) { + + String path = BaseServlet.optional(request, "path", StringUtils.EMPTY); + String headers = BaseServlet.optional(request, "headers", StringUtils.EMPTY); + String expectedResponseCode = BaseServlet.optional(request, "expectedResponseCode", "200"); + + AbstractHealthCheckConfig.Http config = new AbstractHealthCheckConfig.Http(); + config.setType(cktype); + config.setPath(path); + config.setHeaders(headers); + config.setExpectedResponseCode(Integer.parseInt(expectedResponseCode)); + cluster.setHealthChecker(config); + + } else if (AbstractHealthCheckConfig.Mysql.TYPE.equals(cktype)) { + + AbstractHealthCheckConfig.Mysql config = new AbstractHealthCheckConfig.Mysql(); + String user = BaseServlet.optional(request, "user", StringUtils.EMPTY); + String pwd = BaseServlet.optional(request, "pwd", StringUtils.EMPTY); + String cmd = BaseServlet.optional(request, "cmd", StringUtils.EMPTY); + config.setUser(user); + config.setPwd(pwd); + config.setCmd(cmd); + cluster.setHealthChecker(config); + } + + domObj.getClusterMap().put(clusterName, cluster); + } + + // now valid the dom. if failed, exception will be thrown + domObj.setLastModifiedMillis(System.currentTimeMillis()); + domObj.recalculateChecksum(); + domObj.valid(); + + domainsManager.easyAddOrReplaceDom(domObj); + + return "ok"; + } + + @NeedAuth + @RequestMapping("/replaceDom") + public String replaceDom(HttpServletRequest request) throws Exception { + String dom = BaseServlet.required(request, "dom"); + if (domainsManager.getDomain(dom) == null) { + throw new IllegalArgumentException("specified dom doesn't exist, dom : " + dom); + } + + addOrReplaceDom(request); + + Loggers.SRV_LOG.info("dom: " + dom + " is updated, operator: " + + BaseServlet.optional(request, "clientIP", "unknown")); + + return "ok"; + } + + private IpAddress getIPAddress(HttpServletRequest request) { + + String ip = BaseServlet.required(request, "ip"); + String port = BaseServlet.required(request, "port"); + String weight = BaseServlet.optional(request, "weight", "1"); + String cluster = BaseServlet.optional(request, "cluster", UtilsAndCommons.DEFAULT_CLUSTER_NAME); + if (StringUtils.isEmpty(cluster)) { + cluster = BaseServlet.required(request, "clusterName"); + } + + IpAddress ipAddress = new IpAddress(); + ipAddress.setPort(Integer.parseInt(port)); + ipAddress.setIp(ip); + ipAddress.setWeight(Double.parseDouble(weight)); + ipAddress.setClusterName(cluster); + + return ipAddress; + } + + @RequestMapping("/deRegService") + public String deRegService(HttpServletRequest request) throws Exception { + IpAddress ipAddress = getIPAddress(request); + String dom = BaseServlet.optional(request, "serviceName", StringUtils.EMPTY); + if (StringUtils.isEmpty(dom)) { + dom = BaseServlet.required(request, "dom"); + } + + VirtualClusterDomain virtualClusterDomain = (VirtualClusterDomain) domainsManager.getDomain(dom); + if (virtualClusterDomain == null) { + return "ok"; + } + + ParameterMap parameterMap = new ParameterMap<>(); + parameterMap.put("dom", Arrays.asList(dom).toArray(new String[1])); + parameterMap.put("ipList", Arrays.asList(JSON.toJSONString(Arrays.asList(ipAddress))).toArray(new String[1])); + parameterMap.put("json", Arrays.asList("true").toArray(new String[1])); + parameterMap.put("token", Arrays.asList(virtualClusterDomain.getToken()).toArray(new String[1])); + MockHttpRequest mockHttpRequest = MockHttpRequest.buildRequest(parameterMap); + + return remvIP4Dom(mockHttpRequest); + + } + + @SuppressFBWarnings("JLM_JSR166_LOCK_MONITORENTER") + @RequestMapping("/regService") + public String regService(HttpServletRequest request) throws Exception { + + String dom = BaseServlet.required(request, "dom"); + String tenant = BaseServlet.optional(request, "tid", StringUtils.EMPTY); + String app = BaseServlet.optional(request, "app", "DEFAULT"); + String env = BaseServlet.optional(request, "env", StringUtils.EMPTY); + String instanceMetadataJson = BaseServlet.optional(request, "metadata", StringUtils.EMPTY); + + VirtualClusterDomain virtualClusterDomain = (VirtualClusterDomain) domainsManager.getDomain(dom); + + IpAddress ipAddress = getIPAddress(request); + ipAddress.setApp(app); + ipAddress.setLastBeat(System.currentTimeMillis()); + if (StringUtils.isNotEmpty(instanceMetadataJson)) { + ipAddress.setMetadata(JSON.parseObject(instanceMetadataJson, new TypeReference>() { + })); + } + + Loggers.TENANT.debug("reg-service: " + dom + "|" + ipAddress.toJSON() + "|" + env + "|" + tenant + "|" + app); + + if (virtualClusterDomain == null) { + + regDom(request); + + Lock lock = domainsManager.addLock(dom); + + synchronized (lock) { + lock.wait(5000L); + } + + virtualClusterDomain = (VirtualClusterDomain) domainsManager.getDomain(dom); + } + + if (virtualClusterDomain != null) { + + if (!virtualClusterDomain.getClusterMap().containsKey(ipAddress.getClusterName())) { + doAddCluster4Dom(request); + } + + Loggers.TENANT.debug("reg-service", "add ip: " + dom + "|" + ipAddress.toJSON()); + Map stringMap = new HashMap<>(16); + stringMap.put("dom", Arrays.asList(dom).toArray(new String[1])); + stringMap.put("ipList", Arrays.asList(JSON.toJSONString(Arrays.asList(ipAddress))).toArray(new String[1])); + stringMap.put("json", Arrays.asList("true").toArray(new String[1])); + stringMap.put("token", Arrays.asList(virtualClusterDomain.getToken()).toArray(new String[1])); + doAddIP4Dom(MockHttpRequest.buildRequest(stringMap)); + } else { + throw new IllegalArgumentException("dom not found: " + dom); + } + + return "ok"; + } + + + @NeedAuth + @RequestMapping("/updateDom") + public String updateDom(HttpServletRequest request) throws Exception { + // dom + String name = BaseServlet.required(request, "dom"); + VirtualClusterDomain dom = (VirtualClusterDomain) domainsManager.getDomain(name); + if (dom == null) { + throw new IllegalStateException("dom not found"); + } + + RaftPeer leader = RaftCore.getLeader(); + if (leader == null) { + throw new IllegalStateException("not leader at present, cannot update"); + } + + String owners = BaseServlet.optional(request, "owners", StringUtils.EMPTY); + if (!StringUtils.isEmpty(owners)) { + dom.setOwners(Arrays.asList(owners.split(","))); + } + + String token = BaseServlet.optional(request, "newToken", StringUtils.EMPTY); + if (!StringUtils.isEmpty(token)) { + dom.setToken(token); + } + + String enableClientBeat = BaseServlet.optional(request, "enableClientBeat", StringUtils.EMPTY); + if (!StringUtils.isEmpty(enableClientBeat)) { + dom.setEnableClientBeat(Boolean.parseBoolean(enableClientBeat)); + } + + String protectThreshold = BaseServlet.optional(request, "protectThreshold", StringUtils.EMPTY); + if (!StringUtils.isEmpty(protectThreshold)) { + dom.setProtectThreshold(Float.parseFloat(protectThreshold)); + } + + String sitegroup = BaseServlet.optional(request, "sitegroup", StringUtils.EMPTY); + String setSiteGroupForce = BaseServlet.optional(request, "setSiteGroupForce", StringUtils.EMPTY); + if (!StringUtils.isEmpty(sitegroup) || !StringUtils.isEmpty(setSiteGroupForce)) { + Cluster cluster + = dom.getClusterMap().get(BaseServlet.optional(request, "cluster", UtilsAndCommons.DEFAULT_CLUSTER_NAME)); + if (cluster == null) { + throw new IllegalStateException("cluster not found"); + } + + cluster.setSitegroup(sitegroup); + } + + String cktype = BaseServlet.optional(request, "cktype", StringUtils.EMPTY); + if (!StringUtils.isEmpty(cktype)) { + Cluster cluster + = dom.getClusterMap().get(BaseServlet.optional(request, "cluster", UtilsAndCommons.DEFAULT_CLUSTER_NAME)); + if (cluster == null) { + throw new IllegalStateException("cluster not found"); + } + + if (cktype.equals(AbstractHealthCheckProcessor.HTTP_PROCESSOR.getType())) { + AbstractHealthCheckConfig.Http config = new AbstractHealthCheckConfig.Http(); + config.setType(cktype); + config.setPath(BaseServlet.required(request, "path")); + cluster.setHealthChecker(config); + } else if (cktype.equals(AbstractHealthCheckProcessor.TCP_PROCESSOR.getType())) { + AbstractHealthCheckConfig.Tcp config = new AbstractHealthCheckConfig.Tcp(); + config.setType(cktype); + cluster.setHealthChecker(config); + } else if (cktype.equals(AbstractHealthCheckProcessor.MYSQL_PROCESSOR.getType())) { + AbstractHealthCheckConfig.Mysql config = new AbstractHealthCheckConfig.Mysql(); + config.setCmd(BaseServlet.required(request, "cmd")); + config.setPwd(BaseServlet.required(request, "pwd")); + config.setUser(BaseServlet.required(request, "user")); + cluster.setHealthChecker(config); + } else { + throw new IllegalArgumentException("unsupported health check type: " + cktype); + } + + } + + String defIPPort = BaseServlet.optional(request, "defIPPort", StringUtils.EMPTY); + if (!StringUtils.isEmpty(defIPPort)) { + Cluster cluster + = dom.getClusterMap().get(BaseServlet.optional(request, "cluster", UtilsAndCommons.DEFAULT_CLUSTER_NAME)); + if (cluster == null) { + throw new IllegalStateException("cluster not found"); + } + + cluster.setDefIPPort(Integer.parseInt(defIPPort)); + } + + String submask = BaseServlet.optional(request, "submask", StringUtils.EMPTY); + if (!StringUtils.isEmpty(submask)) { + Cluster cluster + = dom.getClusterMap().get(BaseServlet.optional(request, "cluster", UtilsAndCommons.DEFAULT_CLUSTER_NAME)); + if (cluster == null) { + throw new IllegalStateException("cluster not found"); + } + + cluster.setSubmask(submask); + } + + String ipPort4Check = BaseServlet.optional(request, "ipPort4Check", StringUtils.EMPTY); + if (!StringUtils.isEmpty(ipPort4Check)) { + Cluster cluster + = dom.getClusterMap().get(BaseServlet.optional(request, "cluster", UtilsAndCommons.DEFAULT_CLUSTER_NAME)); + if (cluster == null) { + throw new IllegalStateException("cluster not found"); + } + + cluster.setUseIPPort4Check(Boolean.parseBoolean(ipPort4Check)); + } + + String defCkPort = BaseServlet.optional(request, "defCkPort", StringUtils.EMPTY); + if (!StringUtils.isEmpty(defCkPort)) { + Cluster cluster + = dom.getClusterMap().get(BaseServlet.optional(request, "cluster", UtilsAndCommons.DEFAULT_CLUSTER_NAME)); + if (cluster == null) { + throw new IllegalStateException("cluster not found"); + } + + cluster.setDefCkport(Integer.parseInt(defCkPort)); + } + + String useSpecifiedUrl = BaseServlet.optional(request, "useSpecifiedURL", StringUtils.EMPTY); + if (!StringUtils.isEmpty(useSpecifiedUrl)) { + dom.setUseSpecifiedURL(Boolean.parseBoolean(useSpecifiedUrl)); + } + + String resetWeight = BaseServlet.optional(request, "resetWeight", StringUtils.EMPTY); + if (!StringUtils.isEmpty(resetWeight)) { + dom.setResetWeight(Boolean.parseBoolean(resetWeight)); + } + + String enableHealthCheck = BaseServlet.optional(request, "enableHealthCheck", StringUtils.EMPTY); + if (!StringUtils.isEmpty(enableHealthCheck)) { + dom.setEnableHealthCheck(Boolean.parseBoolean(enableHealthCheck)); + } + + String enabled = BaseServlet.optional(request, "enabled", StringUtils.EMPTY); + if (!StringUtils.isEmpty(enabled)) { + dom.setEnabled(Boolean.parseBoolean(enabled)); + } + + String ipDeletedTimeout = BaseServlet.optional(request, "ipDeletedTimeout", "-1"); + + if (!StringUtils.isNotEmpty(ipDeletedTimeout)) { + long timeout = Long.parseLong(ipDeletedTimeout); + if (timeout < VirtualClusterDomain.MINIMUM_IP_DELETE_TIMEOUT) { + throw new IllegalArgumentException("ipDeletedTimeout is too short: " + timeout + ", better longer than 60000"); + } + + dom.setIpDeleteTimeout(timeout); + } + + // now do the validation + dom.setLastModifiedMillis(System.currentTimeMillis()); + dom.recalculateChecksum(); + dom.valid(); + + domainsManager.easyAddOrReplaceDom(dom); + + return "ok"; + } + + @RequestMapping("/hello") + public JSONObject hello(HttpServletRequest request) { + JSONObject result = new JSONObject(); + result.put("msg", "Hello! I am Nacos-Naming and healthy! total dom: diamond " + + domainsManager.getDomMap().size() + ",raft " + domainsManager.getRaftDomMap().size() + + ", local port:" + RunningConfig.getServerPort()); + return result; + } + + + @NeedAuth + @RequestMapping("/remvDom") + public String remvDom(HttpServletRequest request) throws Exception { + String dom = BaseServlet.required(request, "dom"); + if (domainsManager.getDomain(dom) == null) { + throw new IllegalStateException("specified domain doesn't exists."); + } + + domainsManager.easyRemoveDom(dom); + + return "ok"; + } + + @RequestMapping("/getDomsByIP") + public JSONObject getDomsByIP(HttpServletRequest request) { + String ip = BaseServlet.required(request, "ip"); + + Set doms = new HashSet(); + for (String dom : domainsManager.getAllDomNames()) { + Domain domObj = domainsManager.getDomain(dom); + + List ipObjs = domObj.allIPs(); + for (IpAddress ipObj : ipObjs) { + if (ip.contains(":")) { + if (StringUtils.equals(ipObj.getIp() + ":" + ipObj.getPort(), ip)) { + doms.add(domObj.getName()); + } + } else { + if (StringUtils.equals(ipObj.getIp(), ip)) { + doms.add(domObj.getName()); + } + } + } + } + + JSONObject result = new JSONObject(); + + result.put("doms", doms); + + return result; + } + + @RequestMapping("/onAddIP4Dom") + public String onAddIP4Dom(HttpServletRequest request) throws Exception { + if (Switch.getDisableAddIP()) { + throw new AccessControlException("Adding IP for dom is forbidden now."); + } + + String clientIP = BaseServlet.required(request, "clientIP"); + + long term = Long.parseLong(BaseServlet.required(request, "term")); + + if (!RaftCore.isLeader(clientIP)) { + Loggers.RAFT.warn("peer(" + JSON.toJSONString(clientIP) + ") tried to publish " + + "data but wasn't leader, leader: " + JSON.toJSONString(RaftCore.getLeader())); + throw new IllegalStateException("peer(" + clientIP + ") tried to publish " + + "data but wasn't leader"); + } + + if (term < RaftCore.getPeerSet().local().term.get()) { + Loggers.RAFT.warn("out of date publish, pub-term: " + + JSON.toJSONString(clientIP) + ", cur-term: " + JSON.toJSONString(RaftCore.getPeerSet().local())); + throw new IllegalStateException("out of date publish, pub-term:" + + term + ", cur-term: " + RaftCore.getPeerSet().local().term.get()); + } + + RaftCore.getPeerSet().local().resetLeaderDue(); + + final String dom = BaseServlet.required(request, "dom"); + if (domainsManager.getDomain(dom) == null) { + throw new IllegalStateException("dom doesn't exist: " + dom); + } + + boolean updateOnly = Boolean.parseBoolean(BaseServlet.optional(request, "updateOnly", Boolean.FALSE.toString())); + + String ipListString = BaseServlet.required(request, "ipList"); + List newIPs = new ArrayList<>(); + + List ipList; + if (Boolean.parseBoolean(BaseServlet.optional(request, SwitchEntry.PARAM_JSON, Boolean.FALSE.toString()))) { + newIPs = JSON.parseObject(ipListString, new TypeReference>() { + }); + } else { + ipList = Arrays.asList(ipListString.split(",")); + for (String ip : ipList) { + IpAddress ipAddr = IpAddress.fromJSON(ip); + newIPs.add(ipAddr); + } + } + + long timestamp = Long.parseLong(BaseServlet.required(request, "timestamp")); + + if (CollectionUtils.isEmpty(newIPs)) { + throw new IllegalArgumentException("Empty ip list"); + } + + if (updateOnly) { + //make sure every IP is in the dom, otherwise refuse update + List oldIPs = domainsManager.getDomain(dom).allIPs(); + Collection diff = CollectionUtils.subtract(newIPs, oldIPs); + if (diff.size() != 0) { + throw new IllegalArgumentException("these IPs are not present: " + Arrays.toString(diff.toArray()) + + ", if you want to add them, remove updateOnly flag"); + } + } + domainsManager.easyAddIP4Dom(dom, newIPs, timestamp, term); + + return "ok"; + } + + + private String doAddIP4Dom(HttpServletRequest request) throws Exception { + + if (Switch.getDisableAddIP()) { + throw new AccessControlException("Adding IP for dom is forbidden now."); + } + + Map proxyParams = new HashMap<>(16); + for (Map.Entry entry : request.getParameterMap().entrySet()) { + proxyParams.put(entry.getKey(), entry.getValue()[0]); + } + + String ipListString = BaseServlet.required(request, "ipList"); + final List ipList; + List newIPs = new ArrayList<>(); + + if (Boolean.parseBoolean(BaseServlet.optional(request, SwitchEntry.PARAM_JSON, Boolean.FALSE.toString()))) { + ipList = Arrays.asList(ipListString); + newIPs = JSON.parseObject(ipListString, new TypeReference>() { + }); + } else { + ipList = Arrays.asList(ipListString.split(",")); + for (String ip : ipList) { + IpAddress ipAddr = IpAddress.fromJSON(ip); + newIPs.add(ipAddr); + } + } + + if (!RaftCore.isLeader()) { + Loggers.RAFT.info("I'm not leader, will proxy to leader."); + if (RaftCore.getLeader() == null) { + throw new IllegalArgumentException("no leader now."); + } + + RaftPeer leader = RaftCore.getLeader(); + + String server = leader.ip; + if (!server.contains(UtilsAndCommons.CLUSTER_CONF_IP_SPLITER)) { + server = server + UtilsAndCommons.CLUSTER_CONF_IP_SPLITER + RunningConfig.getServerPort(); + } + + String url = "http://" + server + + RunningConfig.getContextPath() + UtilsAndCommons.NACOS_NAMING_CONTEXT + "/api/addIP4Dom"; + HttpClient.HttpResult result1 = HttpClient.httpPost(url, null, proxyParams); + + if (result1.code != HttpURLConnection.HTTP_OK) { + Loggers.SRV_LOG.warn("failed to add ip for dom, caused " + result1.content); + throw new IllegalArgumentException("failed to add ip for dom, caused " + result1.content); + } + + return "ok"; + } + + final String dom = BaseServlet.required(request, "dom"); + if (domainsManager.getDomain(dom) == null) { + throw new IllegalStateException("dom doesn't exist: " + dom); + } + + boolean updateOnly = Boolean.parseBoolean(BaseServlet.optional(request, "updateOnly", "false")); + + if (CollectionUtils.isEmpty(newIPs)) { + throw new IllegalArgumentException("Empty ip list"); + } + + if (updateOnly) { + //make sure every IP is in the dom, otherwise refuse update + List oldIPs = domainsManager.getDomain(dom).allIPs(); + Collection diff = CollectionUtils.subtract(newIPs, oldIPs); + if (diff.size() != 0) { + throw new IllegalArgumentException("these IPs are not present: " + Arrays.toString(diff.toArray()) + + ", if you want to add them, remove updateOnly flag"); + } + } + + String key = UtilsAndCommons.getIPListStoreKey(domainsManager.getDomain(dom)); + + long timestamp = System.currentTimeMillis(); + if (RaftCore.isLeader()) { + RaftCore.OPERATE_LOCK.lock(); + try { + final CountDownLatch countDownLatch = new CountDownLatch(RaftCore.getPeerSet().majorityCount()); + proxyParams.put("clientIP", NetUtils.localIP()); + proxyParams.put("notify", "true"); + + proxyParams.put("term", String.valueOf(RaftCore.getPeerSet().local().term)); + proxyParams.put("timestamp", String.valueOf(timestamp)); + + for (final RaftPeer peer : RaftCore.getPeers()) { + String server = peer.ip; + if (!server.contains(UtilsAndCommons.CLUSTER_CONF_IP_SPLITER)) { + server = server + UtilsAndCommons.CLUSTER_CONF_IP_SPLITER + RunningConfig.getServerPort(); + } + String url = "http://" + server + + RunningConfig.getContextPath() + UtilsAndCommons.NACOS_NAMING_CONTEXT + "/api/onAddIP4Dom"; + HttpClient.asyncHttpPost(url, null, proxyParams, new AsyncCompletionHandler() { + @Override + public Integer onCompleted(Response response) throws Exception { + if (response.getStatusCode() != HttpURLConnection.HTTP_OK) { + Loggers.SRV_LOG.warn("failed to add ip for dom: " + dom + + ",ipList = " + ipList + ",code: " + response.getStatusCode() + + ", caused " + response.getResponseBody() + ", server: " + peer.ip); + return 1; + } + countDownLatch.countDown(); + return 0; + } + }); + } + + if (!countDownLatch.await(UtilsAndCommons.MAX_PUBLISH_WAIT_TIME_MILLIS, TimeUnit.MILLISECONDS)) { + Loggers.RAFT.info("data publish failed, key=" + key, ",notify timeout."); + throw new IllegalArgumentException("data publish failed, key=" + key); + } + + Loggers.EVT_LOG.info("{" + dom + "} {POS} {IP-ADD}" + " new: " + + Arrays.toString(ipList.toArray()) + " operatorIP: " + + BaseServlet.optional(request, "clientIP", "unknown")); + } finally { + RaftCore.OPERATE_LOCK.unlock(); + } + } + + return "ok"; + } + + @NeedAuth + @RequestMapping("/addIP4Dom") + public String addIP4Dom(HttpServletRequest request) throws Exception { + return doAddIP4Dom(request); + } + + @NeedAuth + @RequestMapping("/replaceIP4Dom") + public synchronized String replaceIP4Dom(HttpServletRequest request) throws Exception { + String dom = BaseServlet.required(request, "dom"); + String cluster = BaseServlet.required(request, "cluster"); + + List ips = Arrays.asList(BaseServlet.required(request, "ipList").split(",")); + List ipObjList = new ArrayList(ips.size()); + for (String ip : ips) { + IpAddress ipObj = IpAddress.fromJSON(ip); + if (ipObj == null || ipObj.getPort() <= 0) { + throw new IllegalArgumentException("malformed ip: " + ip + ", format: ip:port[_weight][_cluster]"); + } + + ipObj.setClusterName(cluster); + ipObjList.add(ipObj); + } + + if (CollectionUtils.isEmpty(ipObjList)) { + throw new IllegalArgumentException("empty ip list"); + } + + domainsManager.easyReplaceIP4Dom(dom, cluster, ipObjList); + + return "ok"; + } + + @RequestMapping("/srvAllIP") + public JSONObject srvAllIP(HttpServletRequest request) throws Exception { + + JSONObject result = new JSONObject(); + + if (DistroMapper.getLocalhostIP().equals(UtilsAndCommons.LOCAL_HOST_IP)) { + throw new Exception("invalid localhost ip: " + DistroMapper.getLocalhostIP()); + } + + String dom = BaseServlet.required(request, "dom"); + VirtualClusterDomain domObj = (VirtualClusterDomain) domainsManager.getDomain(dom); + String clusters = BaseServlet.optional(request, "clusters", StringUtils.EMPTY); + + if (domObj == null) { + throw new NacosException(NacosException.NOT_FOUND, "dom not found: " + dom); + } + + checkIfDisabled(domObj); + + long cacheMillis = Switch.getCacheMillis(dom); + + List srvedIPs; + + if (StringUtils.isEmpty(clusters)) { + srvedIPs = domObj.allIPs(); + } else { + srvedIPs = domObj.allIPs(Arrays.asList(clusters.split(","))); + } + + JSONArray ipArray = new JSONArray(); + + for (IpAddress ip : srvedIPs) { + JSONObject ipObj = new JSONObject(); + + ipObj.put("ip", ip.getIp()); + ipObj.put("port", ip.getPort()); + ipObj.put("valid", ip.isValid()); + ipObj.put("weight", ip.getWeight()); + ipObj.put("doubleWeight", ip.getWeight()); + ipObj.put("instanceId", ip.generateInstanceId()); + ipObj.put("metadata", ip.getMetadata()); + ipArray.add(ipObj); + } + + result.put("hosts", ipArray); + + result.put("dom", dom); + result.put("clusters", clusters); + result.put("cacheMillis", cacheMillis); + result.put("lastRefTime", System.currentTimeMillis()); + result.put("checksum", domObj.getChecksum()); + result.put("allIPs", "true"); + + return result; + } + + @RequestMapping("/srvIPXT") + @ResponseBody + public JSONObject srvIPXT(HttpServletRequest request) throws Exception { + + JSONObject result = new JSONObject(); + + if (DistroMapper.getLocalhostIP().equals(UtilsAndCommons.LOCAL_HOST_IP)) { + throw new Exception("invalid localhost ip: " + DistroMapper.getLocalhostIP()); + } + + String dom = BaseServlet.required(request, "dom"); + VirtualClusterDomain domObj = (VirtualClusterDomain) domainsManager.getDomain(dom); + String agent = BaseServlet.optional(request, "header:Client-Version", StringUtils.EMPTY); + String clusters = BaseServlet.optional(request, "clusters", StringUtils.EMPTY); + String clientIP = BaseServlet.optional(request, "clientIP", StringUtils.EMPTY); + Integer udpPort = Integer.parseInt(BaseServlet.optional(request, "udpPort", "0")); + String env = BaseServlet.optional(request, "env", StringUtils.EMPTY); + String error = BaseServlet.optional(request, "unconsistentDom", StringUtils.EMPTY); + boolean isCheck = Boolean.parseBoolean(BaseServlet.optional(request, "isCheck", "false")); + + String app = BaseServlet.optional(request, "app", StringUtils.EMPTY); + + String tenant = BaseServlet.optional(request, "tid", StringUtils.EMPTY); + + boolean healthyOnly = Boolean.parseBoolean(BaseServlet.optional(request, "healthOnly", "false")); + + if (!StringUtils.isEmpty(error)) { + Loggers.ROLE_LOG.info("ENV-NOT-CONSISTENT", error); + } + + if (domObj == null) { + throw new NacosException(NacosException.NOT_FOUND, "dom not found: " + dom); + } + + checkIfDisabled(domObj); + + long cacheMillis = Switch.getCacheMillis(dom); + + // now try to enable the push + try { + if (udpPort > 0 && PushService.canEnablePush(agent)) { + PushService.addClient(dom, + clusters, + agent, + new InetSocketAddress(clientIP, udpPort), + pushDataSource, + tenant, + app); + cacheMillis = Switch.getPushCacheMillis(dom); + } + } catch (Exception e) { + Loggers.SRV_LOG.error("VIPSRV-API", "failed to added push client", e); + cacheMillis = Switch.getCacheMillis(dom); + } + + List srvedIPs; + + srvedIPs = domObj.srvIPs(clientIP, Arrays.asList(StringUtils.split(clusters, ","))); + + if (CollectionUtils.isEmpty(srvedIPs)) { + String msg = "no ip to serve for dom: " + dom; + + Loggers.SRV_LOG.debug(msg); + + if (isCheck) { + result.put("errorMsg", msg); + } else { + throw new NacosException(NacosException.NOT_FOUND, msg); + } + } + + Map> ipMap = new HashMap<>(2); + ipMap.put(Boolean.TRUE, new ArrayList()); + ipMap.put(Boolean.FALSE, new ArrayList()); + + for (IpAddress ip : srvedIPs) { + ipMap.get(ip.isValid()).add(ip); + } + + if (isCheck) { + result.put("reachProtectThreshold", false); + } + + double threshold = domObj.getProtectThreshold(); + + if ((float) ipMap.get(Boolean.TRUE).size() / srvedIPs.size() <= threshold) { + + Loggers.SRV_LOG.warn("protect threshold reached, return all ips, " + + "dom: " + dom); + if (isCheck) { + result.put("reachProtectThreshold", true); + } + + ipMap.get(Boolean.TRUE).addAll(ipMap.get(Boolean.FALSE)); + ipMap.get(Boolean.FALSE).clear(); + } + + if (isCheck) { + result.put("protectThreshold", domObj.getProtectThreshold()); + result.put("reachLocalSiteCallThreshold", false); + + return new JSONObject(); + } + + JSONArray hosts = new JSONArray(); + + for (Map.Entry> entry : ipMap.entrySet()) { + List ips = entry.getValue(); + + if (healthyOnly && !entry.getKey()) { + continue; + } + + for (IpAddress ip : ips) { + JSONObject ipObj = new JSONObject(); + + ipObj.put("ip", ip.getIp()); + ipObj.put("port", ip.getPort()); + ipObj.put("valid", entry.getKey()); + ipObj.put("marked", ip.isMarked()); + ipObj.put("instanceId", ip.generateInstanceId()); + ipObj.put("metadata", ip.getMetadata()); + double weight = ip.getWeight(); + + ipObj.put("weight", ip.getWeight()); + + hosts.add(ipObj); + + } + } + + result.put("hosts", hosts); + + result.put("dom", dom); + result.put("clusters", clusters); + result.put("cacheMillis", cacheMillis); + result.put("lastRefTime", System.currentTimeMillis()); + result.put("checksum", domObj.getChecksum() + System.currentTimeMillis()); + result.put("useSpecifiedURL", false); + result.put("env", env); + + return result; + } + + @NeedAuth + @RequestMapping("/remvIP4Dom") + public String remvIP4Dom(HttpServletRequest request) throws Exception { + String dom = BaseServlet.required(request, "dom"); + String ipListString = BaseServlet.required(request, "ipList"); + List newIPs = new ArrayList<>(); + List ipList = new ArrayList<>(); + if (Boolean.parseBoolean(BaseServlet.optional(request, SwitchEntry.PARAM_JSON, Boolean.FALSE.toString()))) { + newIPs = JSON.parseObject(ipListString, new TypeReference>() { + }); + } else { + ipList = Arrays.asList(ipListString.split(",")); + } + + List ipObjList = new ArrayList<>(ipList.size()); + if (Boolean.parseBoolean(BaseServlet.optional(request, SwitchEntry.PARAM_JSON, Boolean.FALSE.toString()))) { + ipObjList = newIPs; + } else { + for (String ip : ipList) { + ipObjList.add(IpAddress.fromJSON(ip)); + } + } + + domainsManager.easyRemvIP4Dom(dom, ipObjList); + + Loggers.EVT_LOG.info("{" + dom + "} {POS} {IP-REMV}" + " dead: " + + Arrays.toString(ipList.toArray()) + " operator: " + + BaseServlet.optional(request, "clientIP", "unknown")); + + return "ok"; + } + + @RequestMapping("/pushState") + public JSONObject pushState(HttpServletRequest request) { + + JSONObject result = new JSONObject(); + + boolean detail = Boolean.parseBoolean(BaseServlet.optional(request, "detail", "false")); + boolean reset = Boolean.parseBoolean(BaseServlet.optional(request, "reset", "false")); + + List failedPushes = PushService.getFailedPushes(); + int failedPushCount = PushService.getFailedPushCount(); + result.put("succeed", PushService.getTotalPush() - failedPushCount); + result.put("total", PushService.getTotalPush()); + + if (PushService.getTotalPush() > 0) { + result.put("ratio", ((float) PushService.getTotalPush() - failedPushCount) / PushService.getTotalPush()); + } else { + result.put("ratio", 0); + } + + JSONArray dataArray = new JSONArray(); + if (detail) { + for (PushService.Receiver.AckEntry entry : failedPushes) { + try { + dataArray.add(new String(entry.origin.getData(), "UTF-8")); + } catch (UnsupportedEncodingException e) { + dataArray.add("[encoding failure]"); + } + } + result.put("data", dataArray); + } + + if (reset) { + PushService.resetPushState(); + } + + result.put("reset", reset); + + return result; + } + + + ReentrantLock lock = new ReentrantLock(); + + @NeedAuth + @RequestMapping("/updateSwitch") + public String updateSwitch(HttpServletRequest request) throws Exception { + Boolean debug = Boolean.parseBoolean(BaseServlet.optional(request, "debug", "false")); + + if (!RaftCore.isLeader() && !debug) { + Map tmpParams = new HashMap<>(16); + for (Map.Entry entry : request.getParameterMap().entrySet()) { + tmpParams.put(entry.getKey(), entry.getValue()[0]); + } + + RaftProxy.proxyGET(UtilsAndCommons.NACOS_NAMING_CONTEXT + "/api/updateSwitch", tmpParams); + return "ok"; + } + + try { + lock.lock(); + String entry = BaseServlet.required(request, "entry"); + + Datum datum = RaftCore.getDatum(UtilsAndCommons.DOMAINS_DATA_ID + ".00-00---000-VIPSRV_SWITCH_DOMAIN-000---00-00"); + SwitchDomain switchDomain = null; + + if (datum != null) { + switchDomain = JSON.parseObject(datum.value, SwitchDomain.class); + } else { + Loggers.SRV_LOG.warn("datum: " + UtilsAndCommons.DOMAINS_DATA_ID + ".00-00---000-VIPSRV_SWITCH_DOMAIN-000---00-00 is null"); + } + + if (SwitchEntry.BATCH.equals(entry)) { + //batch update + SwitchDomain dom = JSON.parseObject(BaseServlet.required(request, "json"), SwitchDomain.class); + dom.setEnableStandalone(Switch.isEnableStandalone()); + if (dom.httpHealthParams.getMin() < SwitchDomain.HttpHealthParams.MIN_MIN + || dom.tcpHealthParams.getMin() < SwitchDomain.HttpHealthParams.MIN_MIN) { + + throw new IllegalArgumentException("min check time for http or tcp is too small(<500)"); + } + + if (dom.httpHealthParams.getMax() < SwitchDomain.HttpHealthParams.MIN_MAX + || dom.tcpHealthParams.getMax() < SwitchDomain.HttpHealthParams.MIN_MAX) { + + throw new IllegalArgumentException("max check time for http or tcp is too small(<3000)"); + } + + if (dom.httpHealthParams.getFactor() < 0 + || dom.httpHealthParams.getFactor() > 1 + || dom.tcpHealthParams.getFactor() < 0 + || dom.tcpHealthParams.getFactor() > 1) { + + throw new IllegalArgumentException("malformed factor"); + } + + Switch.setDom(dom); + if (!debug) { + Switch.save(); + } + + return "ok"; + } + + if (switchDomain != null) { + Switch.setDom(switchDomain); + } + + if (entry.equals(SwitchEntry.DISTRO_THRESHOLD)) { + Float threshold = Float.parseFloat(BaseServlet.required(request, "distroThreshold")); + + if (threshold <= 0) { + throw new IllegalArgumentException("distroThreshold can not be zero or negative: " + threshold); + } + + Switch.setDistroThreshold(threshold); + + if (!debug) { + Switch.save(); + } + return "ok"; + } + + + if (entry.equals(SwitchEntry.ENABLE_ALL_DOM_NAME_CACHE)) { + Boolean enable = Boolean.parseBoolean(BaseServlet.required(request, "enableAllDomNameCache")); + Switch.setAllDomNameCache(enable); + + if (!debug) { + Switch.save(); + } + + return "ok"; + } + + if (entry.equals(SwitchEntry.INCREMENTAL_LIST)) { + String action = BaseServlet.required(request, "action"); + List doms = Arrays.asList(BaseServlet.required(request, "incrementalList").split(",")); + + if (action.equals(SwitchEntry.ACTION_UPDATE)) { + Switch.getIncrementalList().addAll(doms); + } else if (action.equals(SwitchEntry.ACTION_DELETE)) { + Switch.getIncrementalList().removeAll(doms); + } else { + throw new IllegalArgumentException("action is not allowed: " + action); + } + + if (!debug) { + Switch.save(); + } + + return "ok"; + } + + if (entry.equals(SwitchEntry.HEALTH_CHECK_WHITLE_LIST)) { + String action = BaseServlet.required(request, "action"); + List whiteList = Arrays.asList(BaseServlet.required(request, "healthCheckWhiteList").split(",")); + + if (action.equals(SwitchEntry.ACTION_UPDATE)) { + Switch.getHealthCheckWhiteList().addAll(whiteList); + if (!debug) { + Switch.save(); + } + + return "ok"; + } + + if (action.equals(SwitchEntry.ACTION_DELETE)) { + Switch.getHealthCheckWhiteList().removeAll(whiteList); + if (!debug) { + Switch.save(); + } + return "ok"; + } + } + + if (entry.equals(SwitchEntry.CLIENT_BEAT_INTERVAL)) { + long clientBeatInterval = Long.parseLong(BaseServlet.required(request, "clientBeatInterval")); + Switch.setClientBeatInterval(clientBeatInterval); + + if (!debug) { + Switch.save(); + } + return "ok"; + } + + if (entry.equals(SwitchEntry.PUSH_VERSION)) { + String type = BaseServlet.required(request, "type"); + String version = BaseServlet.required(request, "version"); + + if (!version.matches(UtilsAndCommons.VERSION_STRING_SYNTAX)) { + throw new IllegalArgumentException("illegal version, must match: " + UtilsAndCommons.VERSION_STRING_SYNTAX); + } + + if (StringUtils.equals(SwitchEntry.CLIENT_JAVA, type)) { + Switch.setPushJavaVersion(version); + } else if (StringUtils.equals(SwitchEntry.CLIENT_PYTHON, type)) { + Switch.setPushPythonVersion(version); + } else if (StringUtils.equals(SwitchEntry.CLIENT_C, type)) { + Switch.setPushCVersion(version); + } else { + throw new IllegalArgumentException("unsupported client type: " + type); + } + + if (!debug) { + Switch.save(); + } + return "ok"; + } + + if (entry.equals(SwitchEntry.TRAFFIC_SCHEDULING_VERSION)) { + String type = BaseServlet.required(request, "type"); + String version = BaseServlet.required(request, "version"); + + if (!version.matches(UtilsAndCommons.VERSION_STRING_SYNTAX)) { + throw new IllegalArgumentException("illegal version, must match: " + UtilsAndCommons.VERSION_STRING_SYNTAX); + } + + if (StringUtils.equals(SwitchEntry.CLIENT_JAVA, type)) { + Switch.setTrafficSchedulingJavaVersion(version); + } else if (StringUtils.equals(SwitchEntry.CLIENT_PYTHON, type)) { + Switch.setTrafficSchedulingPythonVersion(version); + } else if (StringUtils.equals(SwitchEntry.CLIENT_C, type)) { + Switch.setTrafficSchedulingCVersion(version); + } else if (StringUtils.equals(SwitchEntry.CLIENT_TENGINE, type)) { + Switch.setTrafficSchedulingTengineVersion(version); + } else { + throw new IllegalArgumentException("unsupported client type: " + type); + } + + if (!debug) { + Switch.save(); + } + return "ok"; + } + + if (entry.equals(SwitchEntry.PUSH_CACHE_MILLIS)) { + String dom = BaseServlet.optional(request, "dom", StringUtils.EMPTY); + Long cacheMillis = Long.parseLong(BaseServlet.required(request, "millis")); + + if (cacheMillis < SwitchEntry.MIN_PUSH_CACHE_TIME_MIILIS) { + throw new IllegalArgumentException("min cache time for http or tcp is too small(<10000)"); + } + + Switch.setPushCacheMillis(dom, cacheMillis); + if (!debug) { + Switch.save(); + } + return "ok"; + } + + // extremely careful while modifying this, cause it will affect all clients without pushing enabled + if (entry.equals(SwitchEntry.DEFAULT_CACHE_MILLIS)) { + String dom = BaseServlet.optional(request, "dom", StringUtils.EMPTY); + Long cacheMillis = Long.parseLong(BaseServlet.required(request, "millis")); + + if (cacheMillis < SwitchEntry.MIN_CACHE_TIME_MIILIS) { + throw new IllegalArgumentException("min default cache time is too small(<1000)"); + } + + Switch.setCacheMillis(dom, cacheMillis); + if (!debug) { + Switch.save(); + } + return "ok"; + } + + if (entry.equals(SwitchEntry.MASTERS)) { + List masters = Arrays.asList(BaseServlet.required(request, "names").split(",")); + + Switch.setMasters(masters); + if (!debug) { + Switch.save(); + } + return "ok"; + } + + if (entry.equals(SwitchEntry.DISTRO)) { + boolean enabled = Boolean.parseBoolean(BaseServlet.required(request, "enabled")); + + Switch.setDistroEnabled(enabled); + if (!debug) { + Switch.save(); + } + return "ok"; + } + + if (entry.equals(SwitchEntry.CHECK)) { + boolean enabled = Boolean.parseBoolean(BaseServlet.required(request, "enabled")); + + Switch.setHeathCheckEnabled(enabled); + if (!debug) { + Switch.save(); + } + return "ok"; + } + + if (entry.equals(SwitchEntry.DOM_STATUS_SYNC_PERIOD)) { + Long millis = Long.parseLong(BaseServlet.required(request, "millis")); + + if (millis < SwitchEntry.MIN_DOM_SYNC_TIME_MIILIS) { + throw new IllegalArgumentException("domStatusSynchronizationPeriodMillis is too small(<5000)"); + } + + Switch.setDomStatusSynchronizationPeriodMillis(millis); + if (!debug) { + Switch.save(); + } + return "ok"; + } + + if (entry.equals(SwitchEntry.SERVER_STATUS_SYNC_PERIOD)) { + Long millis = Long.parseLong(BaseServlet.required(request, "millis")); + + if (millis < SwitchEntry.MIN_SERVER_SYNC_TIME_MIILIS) { + throw new IllegalArgumentException("serverStatusSynchronizationPeriodMillis is too small(<15000)"); + } + + Switch.setServerStatusSynchronizationPeriodMillis(millis); + if (!debug) { + Switch.save(); + } + return "ok"; + } + + if (entry.equals(SwitchEntry.HEALTH_CHECK_TIMES)) { + Integer times = Integer.parseInt(BaseServlet.required(request, "times")); + + Switch.setCheckTimes(times); + if (!debug) { + Switch.save(); + } + return "ok"; + } + + if (entry.equals(SwitchEntry.DISABLE_ADD_IP)) { + boolean disableAddIP = Boolean.parseBoolean(BaseServlet.required(request, "disableAddIP")); + + Switch.setDisableAddIP(disableAddIP); + if (!debug) { + Switch.save(); + } + return "ok"; + } + + if (entry.equals(SwitchEntry.ENABLE_CACHE)) { + boolean enableCache = Boolean.parseBoolean(BaseServlet.required(request, "enableCache")); + + Switch.setEnableCache(enableCache); + if (!debug) { + Switch.save(); + } + return "ok"; + } + + if (entry.equals(SwitchEntry.SEND_BEAT_ONLY)) { + boolean sendBeatOnly = Boolean.parseBoolean(BaseServlet.required(request, "sendBeatOnly")); + + Switch.setSendBeatOnly(sendBeatOnly); + if (!debug) { + Switch.save(); + } + return "ok"; + } + + if (entry.equals(SwitchEntry.LIMITED_URL_MAP)) { + Map limitedUrlMap = new HashMap<>(16); + String limitedUrls = BaseServlet.required(request, "limitedUrls"); + + if (!StringUtils.isEmpty(limitedUrls)) { + String[] entries = limitedUrls.split(","); + for (int i = 0; i < entries.length; i++) { + String[] parts = entries[i].split(":"); + if (parts.length < 2) { + throw new IllegalArgumentException("invalid input for limited urls"); + } + + String limitedUrl = parts[0]; + if (StringUtils.isEmpty(limitedUrl)) { + throw new IllegalArgumentException("url can not be empty, url: " + limitedUrl); + } + + int statusCode = Integer.parseInt(parts[1]); + if (statusCode <= 0) { + throw new IllegalArgumentException("illegal normal status code: " + statusCode); + } + + limitedUrlMap.put(limitedUrl, statusCode); + + } + + Switch.setLimitedUrlMap(limitedUrlMap); + if (!debug) { + Switch.save(); + } + return "ok"; + } + } + + if (entry.equals(SwitchEntry.ENABLE_STANDALONE)) { + String enable = BaseServlet.required(request, "enableStandalone"); + + if (!StringUtils.isNotEmpty(enable)) { + Switch.setEnableStandalone(Boolean.parseBoolean(enable)); + } + + if (!debug) { + Switch.save(); + } + + return "ok"; + } + + + throw new IllegalArgumentException("update entry not found: " + entry); + } finally { + lock.unlock(); + } + + + } + + @RequestMapping("/checkStatus") + public JSONObject checkStatus(HttpServletRequest request) { + + JSONObject result = new JSONObject(); + result.put("healthCheckEnabled", Switch.isHealthCheckEnabled()); + result.put("allDoms", domainsManager.getAllDomNames()); + + List doms = new ArrayList(); + for (String dom : domainsManager.getAllDomNames()) { + if (DistroMapper.responsible(dom)) { + doms.add(dom); + } + } + + result.put("respDoms", doms); + + return result; + } + + public void checkIfDisabled(VirtualClusterDomain domObj) throws Exception { + if (!domObj.getEnabled()) { + throw new Exception("domain is disabled now."); + } + } + + @RequestMapping("/switches") + public JSONObject switches(HttpServletRequest request) { + + return JSON.parseObject(Switch.getDom().toJSON()); + } + + @RequestMapping("/getVersion") + public JSONObject getVersion(HttpServletRequest request) throws IOException { + + JSONObject result = new JSONObject(); + InputStream is = ApiCommands.class.getClassLoader().getResourceAsStream("application.properties"); + Properties properties = new Properties(); + properties.load(is); + + try (InputStreamReader releaseNode = + new InputStreamReader(ApiCommands.class.getClassLoader().getResourceAsStream("changelog.properties"), "UTF-8")) { + + Properties properties1 = new Properties(); + properties1.load(releaseNode); + + result.put("server version", properties.getProperty("version")); + result.put("change log", properties1.getProperty(properties.getProperty("version"))); + } + return result; + } + + @RequestMapping("/getAllChangeLog") + public JSONObject getAllChangeLog(HttpServletRequest request) throws Exception { + + JSONObject result = new JSONObject(); + try (InputStreamReader releaseNode = + new InputStreamReader(ApiCommands.class.getClassLoader().getResourceAsStream("changelog.properties"), "UTF-8")) { + + Properties properties1 = new Properties(); + properties1.load(releaseNode); + + for (String name : properties1.stringPropertyNames()) { + result.put(name, properties1.getProperty(name)); + } + } + + return result; + } + + @RequestMapping("/allDomNames") + public JSONObject allDomNames(HttpServletRequest request) throws Exception { + + boolean responsibleOnly = Boolean.parseBoolean(BaseServlet.optional(request, "responsibleOnly", "false")); + boolean withOwner = Boolean.parseBoolean((BaseServlet.optional(request, "withOwner", "false"))); + + List doms = new ArrayList(); + Set domSet; + + domSet = domainsManager.getAllDomNames(); + for (String dom : domSet) { + if (DistroMapper.responsible(dom) || !responsibleOnly) { + if (withOwner) { + doms.add(dom + ":" + ArrayUtils.toString(domainsManager.getDomain(dom).getOwners())); + } else { + doms.add(dom); + } + } + } + + JSONObject result = new JSONObject(); + + result.put("doms", doms); + result.put("count", doms.size()); + + return result; + } + + @RequestMapping("/searchDom") + public JSONObject searchDom(HttpServletRequest request) { + + JSONObject result = new JSONObject(); + String expr = BaseServlet.required(request, "expr"); + + List doms + = domainsManager.searchDomains(".*" + expr + ".*"); + + if (CollectionUtils.isEmpty(doms)) { + result.put("doms", Collections.emptyList()); + return result; + } + + JSONArray domArray = new JSONArray(); + for (Domain dom : doms) { + domArray.add(dom.getName()); + } + + result.put("doms", domArray); + + return result; + } + + @RequestMapping("/getWeightsByIP") + public JSONObject getWeightsByIP(HttpServletRequest request) { + String ip = BaseServlet.required(request, "ip"); + + Map> dom2IPList = new HashMap>(1024); + for (String dom : domainsManager.getAllDomNames()) { + Domain domObj = domainsManager.getDomain(dom); + + List ipObjs = domObj.allIPs(); + for (IpAddress ipObj : ipObjs) { + if (StringUtils.startsWith(ipObj.getIp() + ":" + ipObj.getPort(), ip)) { + List list = dom2IPList.get(domObj.getName()); + + if (CollectionUtils.isEmpty(list)) { + list = new ArrayList<>(); + dom2IPList.put(domObj.getName(), list); + } + list.add(ipObj); + } + } + } + + JSONObject result = new JSONObject(); + JSONArray ipArray = new JSONArray(); + for (Map.Entry> entry : dom2IPList.entrySet()) { + for (IpAddress ipAddress : entry.getValue()) { + + JSONObject packet = new JSONObject(); + packet.put("dom", entry.getKey()); + packet.put("ip", ipAddress.getIp()); + packet.put("weight", ipAddress.getWeight()); + packet.put("port", ipAddress.getPort()); + packet.put("cluster", ipAddress.getClusterName()); + + ipArray.add(packet); + } + } + + result.put("ips", ipArray); + + result.put("code", 200); + result.put("successful", "success"); + + return result; + } + + + private Cluster getClusterFromJson(String json) { + JSONObject object = JSON.parseObject(json); + String type = object.getJSONObject("healthChecker").getString("type"); + AbstractHealthCheckConfig abstractHealthCheckConfig; + + if (type.equals(HealthCheckType.HTTP.name())) { + abstractHealthCheckConfig = JSON.parseObject(object.getString("healthChecker"), AbstractHealthCheckConfig.Http.class); + } else if (type.equals(HealthCheckType.TCP.name())) { + abstractHealthCheckConfig = JSON.parseObject(object.getString("healthChecker"), AbstractHealthCheckConfig.Tcp.class); + } else if (type.equals(HealthCheckType.MYSQL.name())) { + abstractHealthCheckConfig = JSON.parseObject(object.getString("healthChecker"), AbstractHealthCheckConfig.Mysql.class); + } else { + throw new IllegalArgumentException("can not prase cluster from json: " + json); + } + + Cluster cluster = JSON.parseObject(json, Cluster.class); + + cluster.setHealthChecker(abstractHealthCheckConfig); + return cluster; + } + + public String doAddCluster4Dom(HttpServletRequest request) throws Exception { + + String dom = BaseServlet.required(request, "dom"); + String json = BaseServlet.optional(request, "clusterJson", StringUtils.EMPTY); + + VirtualClusterDomain domObj = (VirtualClusterDomain) domainsManager.getDomain(dom); + + if (domObj == null) { + throw new IllegalArgumentException("dom not found: " + dom); + } + + Cluster cluster = new Cluster(); + + if (!StringUtils.isEmpty(json)) { + try { + cluster = getClusterFromJson(json); + + } catch (Exception e) { + Loggers.SRV_LOG.warn("ADD-CLUSTER", "failed to parse json, try old format."); + } + } else { + String cktype = BaseServlet.optional(request, "cktype", "TCP"); + String clusterName = BaseServlet.optional(request, "clusterName", UtilsAndCommons.DEFAULT_CLUSTER_NAME); + String ipPort4Check = BaseServlet.optional(request, "ipPort4Check", "true"); + String path = BaseServlet.optional(request, "path", StringUtils.EMPTY); + String headers = BaseServlet.optional(request, "headers", StringUtils.EMPTY); + String nodegroup = BaseServlet.optional(request, "nodegroup", StringUtils.EMPTY); + String expectedResponseCode = BaseServlet.optional(request, "expectedResponseCode", "200"); + int defIPPort = NumberUtils.toInt(BaseServlet.optional(request, "defIPPort", "-1")); + int defCkport = NumberUtils.toInt(BaseServlet.optional(request, "defCkport", "80")); + String siteGroup = BaseServlet.optional(request, "siteGroup", StringUtils.EMPTY); + String submask = BaseServlet.optional(request, "submask", StringUtils.EMPTY); + String clusterMetadataJson = BaseServlet.optional(request, "clusterMetadata", StringUtils.EMPTY); + cluster.setName(clusterName); + + cluster.setLegacySyncConfig(nodegroup); + + cluster.setUseIPPort4Check(Boolean.parseBoolean(ipPort4Check)); + cluster.setDefIPPort(defIPPort); + cluster.setDefCkport(defCkport); + + if (StringUtils.isNotEmpty(clusterMetadataJson)) { + cluster.setMetadata(JSON.parseObject(clusterMetadataJson, new TypeReference>() { + })); + } + + if (StringUtils.equals(cktype, HealthCheckType.HTTP.name())) { + AbstractHealthCheckConfig.Http config = new AbstractHealthCheckConfig.Http(); + config.setType(cktype); + config.setPath(path); + config.setHeaders(headers); + config.setExpectedResponseCode(Integer.parseInt(expectedResponseCode)); + cluster.setHealthChecker(config); + } else if (StringUtils.equals(cktype, HealthCheckType.TCP.name())) { + AbstractHealthCheckConfig.Tcp config = new AbstractHealthCheckConfig.Tcp(); + config.setType(cktype); + cluster.setHealthChecker(config); + } else if (StringUtils.equals(cktype, HealthCheckType.MYSQL.name())) { + AbstractHealthCheckConfig.Mysql config = new AbstractHealthCheckConfig.Mysql(); + String cmd = BaseServlet.required(request, "cmd"); + String pwd = BaseServlet.required(request, "pwd"); + String user = BaseServlet.required(request, "user"); + + config.setType(cktype); + config.setCmd(cmd); + config.setPwd(pwd); + config.setUser(user); + cluster.setHealthChecker(config); + } + cluster.setSitegroup(siteGroup); + + if (!StringUtils.isEmpty(submask)) { + cluster.setSubmask(submask); + } + } + cluster.setDom(domObj); + cluster.init(); + + if (domObj.getClusterMap().containsKey(cluster.getName())) { + domObj.getClusterMap().get(cluster.getName()).update(cluster); + } else { + domObj.getClusterMap().put(cluster.getName(), cluster); + } + + domObj.setLastModifiedMillis(System.currentTimeMillis()); + domObj.recalculateChecksum(); + domObj.valid(); + + domainsManager.easyAddOrReplaceDom(domObj); + + return "ok"; + } + + @NeedAuth + @RequestMapping("/addCluster4Dom") + public String addCluster4Dom(HttpServletRequest request) throws Exception { + return doAddCluster4Dom(request); + } + + /** + * This API returns dom names only. you should use API: dom to retrieve dom details + */ + @RequestMapping("/domList") + public JSONObject domList(HttpServletRequest request) { + + JSONObject result = new JSONObject(); + + int page = Integer.parseInt(BaseServlet.required(request, "startPg")); + int pageSize = Integer.parseInt(BaseServlet.required(request, "pgSize")); + + List doms = domainsManager.getPagedDom(page, pageSize); + if (CollectionUtils.isEmpty(doms)) { + result.put("domList", Collections.emptyList()); + return result; + } + + JSONArray domArray = new JSONArray(); + for (Domain dom : doms) { + domArray.add(dom.getName()); + } + + result.put("domList", domArray); + + return result; + } + + @RequestMapping("/distroStatus") + public JSONObject distroStatus(HttpServletRequest request) { + + JSONObject result = new JSONObject(); + String action = BaseServlet.optional(request, "action", "view"); + + if (StringUtils.equals(SwitchEntry.ACTION_VIEW, action)) { + result.put("status", DistroMapper.getDistroConfig()); + return result; + } + + if (StringUtils.equals(SwitchEntry.ACTION_CLEAN, action)) { + DistroMapper.clean(); + return result; + } + + return result; + } + + @RequestMapping("/metrics") + public JSONObject metrics(HttpServletRequest request) { + + JSONObject result = new JSONObject(); + + int domCount = domainsManager.getDomCount(); + int ipCount = domainsManager.getIPCount(); + + int responsibleDomCount = domainsManager.getResponsibleDoms().size(); + int responsibleIPCount = domainsManager.getResponsibleIPCount(); + + result.put("domCount", domCount); + result.put("ipCount", ipCount); + result.put("responsibleDomCount", responsibleDomCount); + result.put("responsibleIPCount", responsibleIPCount); + result.put("cpu", SystemUtil.getCPU()); + result.put("load", SystemUtil.getLoad()); + result.put("mem", SystemUtil.getMem()); + + return result; + } + + @RequestMapping("/updateClusterConf") + public JSONObject updateClusterConf(HttpServletRequest request) throws IOException { + + JSONObject result = new JSONObject(); + + String ipSpliter = ","; + + String ips = BaseServlet.optional(request, "ips", ""); + String action = BaseServlet.required(request, "action"); + + if (SwitchEntry.ACTION_ADD.equals(action)) { + + List oldList = + IoUtils.readLines(new InputStreamReader(new FileInputStream(UtilsAndCommons.getConfFile()), "UTF-8")); + StringBuilder sb = new StringBuilder(); + for (String ip : oldList) { + sb.append(ip).append("\r\n"); + } + for (String ip : ips.split(ipSpliter)) { + sb.append(ip).append("\r\n"); + } + + Loggers.SRV_LOG.info("UPDATE-CLUSTER", "new ips:" + sb.toString()); + IoUtils.writeStringToFile(new File(UtilsAndCommons.getConfFile()), sb.toString(), "utf-8"); + return result; + } + + if (SwitchEntry.ACTION_REPLACE.equals(action)) { + + StringBuilder sb = new StringBuilder(); + for (String ip : ips.split(ipSpliter)) { + sb.append(ip).append("\r\n"); + } + Loggers.SRV_LOG.info("UPDATE-CLUSTER", "new ips:" + sb.toString()); + IoUtils.writeStringToFile(new File(UtilsAndCommons.getConfFile()), sb.toString(), "utf-8"); + return result; + } + + if (SwitchEntry.ACTION_DELETE.equals(action)) { + + Set removeIps = new HashSet<>(); + for (String ip : ips.split(ipSpliter)) { + removeIps.add(ip); + } + + List oldList = + IoUtils.readLines(new InputStreamReader(new FileInputStream(UtilsAndCommons.getConfFile()), "utf-8")); + + Iterator iterator = oldList.iterator(); + + while (iterator.hasNext()) { + + String ip = iterator.next(); + if (removeIps.contains(ip)) { + iterator.remove(); + } + } + + StringBuilder sb = new StringBuilder(); + for (String ip : oldList) { + sb.append(ip).append("\r\n"); + } + + IoUtils.writeStringToFile(new File(UtilsAndCommons.getConfFile()), sb.toString(), "utf-8"); + + return result; + } + + if (SwitchEntry.ACTION_VIEW.equals(action)) { + + List oldList = + IoUtils.readLines(new InputStreamReader(new FileInputStream(UtilsAndCommons.getConfFile()), "utf-8")); + result.put("list", oldList); + + return result; + } + + throw new InvalidParameterException("action is not qualified, action: " + action); + + } + + @RequestMapping("/serverStatus") + public String serverStatus(HttpServletRequest request) { + String serverStatus = BaseServlet.required(request, "serverStatus"); + DistroMapper.onReceiveServerStatus(serverStatus); + + return "ok"; + } + + @RequestMapping("/reCalculateCheckSum4Dom") + public JSONObject reCalculateCheckSum4Dom(HttpServletRequest request) { + String dom = BaseServlet.required(request, "dom"); + VirtualClusterDomain virtualClusterDomain = (VirtualClusterDomain) domainsManager.getDomain(dom); + + if (virtualClusterDomain == null) { + throw new IllegalArgumentException("dom not found"); + } + + virtualClusterDomain.recalculateChecksum(); + + JSONObject result = new JSONObject(); + + result.put("checksum", virtualClusterDomain.getChecksum()); + + return result; + } + + @RequestMapping("/getDomString4MD5") + public JSONObject getDomString4MD5(HttpServletRequest request) throws NacosException { + + JSONObject result = new JSONObject(); + String dom = BaseServlet.required(request, "dom"); + VirtualClusterDomain virtualClusterDomain = (VirtualClusterDomain) domainsManager.getDomain(dom); + + if (virtualClusterDomain == null) { + throw new NacosException(NacosException.NOT_FOUND, "dom not found"); + } + + result.put("domString", virtualClusterDomain.getDomString()); + + return result; + } + + @RequestMapping("/getResponsibleServer4Dom") + public JSONObject getResponsibleServer4Dom(HttpServletRequest request) { + String dom = BaseServlet.required(request, "dom"); + VirtualClusterDomain virtualClusterDomain = (VirtualClusterDomain) domainsManager.getDomain(dom); + + if (virtualClusterDomain == null) { + throw new IllegalArgumentException("dom not found"); + } + + JSONObject result = new JSONObject(); + + result.put("responsibleServer", DistroMapper.mapSrv(dom)); + + return result; + } + + @RequestMapping("/getHealthyServerList") + public JSONObject getHealthyServerList(HttpServletRequest request) { + + JSONObject result = new JSONObject(); + result.put("healthyList", DistroMapper.getHealthyList()); + + return result; + } + + @RequestMapping("/responsible") + public JSONObject responsible(HttpServletRequest request) { + String dom = BaseServlet.required(request, "dom"); + VirtualClusterDomain virtualClusterDomain = (VirtualClusterDomain) domainsManager.getDomain(dom); + + if (virtualClusterDomain == null) { + throw new IllegalArgumentException("dom not found"); + } + + JSONObject result = new JSONObject(); + + result.put("responsible", DistroMapper.responsible(dom)); + + return result; + } + + @RequestMapping("/domServeStatus") + public JSONObject domServeStatus(HttpServletRequest request) { + + JSONObject result = new JSONObject(); + //all ips, sites, disabled site, checkserver, appName + String dom = BaseServlet.required(request, "dom"); + VirtualClusterDomain virtualClusterDomain = (VirtualClusterDomain) domainsManager.getDomain(dom); + + Map data = new HashMap<>(2); + + if (virtualClusterDomain == null) { + result.put("success", false); + result.put("data", data); + result.put("errMsg", "dom does not exisit."); + return result; + } + + List ipAddresses = virtualClusterDomain.allIPs(); + List> allIPs = new ArrayList<>(); + + for (IpAddress ip : ipAddresses) { + + Map ipPac = new HashMap<>(16); + ipPac.put("ip", ip.getIp()); + ipPac.put("valid", ip.isValid()); + ipPac.put("port", ip.getPort()); + ipPac.put("marked", ip.isMarked()); + ipPac.put("cluster", ip.getClusterName()); + ipPac.put("weight", ip.getWeight()); + + allIPs.add(ipPac); + } + + List checkServers = Arrays.asList(DistroMapper.mapSrv(dom)); + + data.put("ips", allIPs); + data.put("checkers", checkServers); + result.put("data", data); + result.put("success", true); + result.put("errMsg", StringUtils.EMPTY); + + return result; + } + + @RequestMapping("/domStatus") + public String domStatus(HttpServletRequest request) { + //format: dom1@@checksum@@@dom2@@checksum + String domsStatusString = BaseServlet.required(request, "domsStatus"); + String serverIP = BaseServlet.optional(request, "clientIP", ""); + + if (!NamingProxy.getServers().contains(serverIP)) { + throw new IllegalArgumentException("ip: " + serverIP + " is not in serverlist"); + } + + try { + DomainsManager.DomainChecksum checksums = JSON.parseObject(domsStatusString, DomainsManager.DomainChecksum.class); + if (checksums == null) { + Loggers.SRV_LOG.warn("DOMAIN-STATUS", "receive malformed data: " + null); + return "fail"; + } + + for (Map.Entry entry : checksums.domName2Checksum.entrySet()) { + if (entry == null || StringUtils.isEmpty(entry.getKey()) || StringUtils.isEmpty(entry.getValue())) { + continue; + } + String dom = entry.getKey(); + String checksum = entry.getValue(); + Domain domain = domainsManager.getDomain(dom); + + if (domain == null) { + continue; + } + + domain.recalculateChecksum(); + + if (!checksum.equals(domain.getChecksum())) { + Loggers.SRV_LOG.debug("checksum of " + dom + " is not consistent, remote: " + serverIP + ",checksum: " + checksum + ", local: " + domain.getChecksum()); + domainsManager.addUpdatedDom2Queue(dom, serverIP, checksum); + } + } + } catch (Exception e) { + Loggers.SRV_LOG.warn("DOMAIN-STATUS", "receive malformed data: " + domsStatusString, e); + } + + return "ok"; + } + + @RequestMapping("/checkDataConsistence") + public JSONObject checkDataConsistence(HttpServletRequest request) throws Exception { + + JSONObject result = new JSONObject(); + String domName = BaseServlet.optional(request, "dom", StringUtils.EMPTY); + Boolean checkConsistence = Boolean.parseBoolean(BaseServlet.optional(request, "checkConsistence", "true")); + + if (!checkConsistence) { + request.getParameterMap().put("isCheck", (String[]) Arrays.asList("true").toArray()); + + srvIPXT(request); + srvAllIP(request); + return result; + } + + if (StringUtils.isEmpty(domName)) { + List domNames = new ArrayList(domainsManager.getAllDomNames()); + domName = domNames.get((int) (System.currentTimeMillis() % domNames.size())); + } + + Domain domain = domainsManager.getDomain(domName); + List diff = new ArrayList(); + String localDomString = ""; + + for (String ip : NamingProxy.getServers()) { + Map tmpParams = new HashMap(16); + + tmpParams.put("dom", domName); + tmpParams.put("redirect", "1"); + + String domString; + try { + domString = NamingProxy.reqAPI("dom", tmpParams, ip, false); + JSONObject jsonObject = JSON.parseObject(domString); + + if (!jsonObject.getString("checksum").equals(domain.getChecksum())) { + diff.add(ip + "_" + domString); + } + + if (ip.equals(NetUtils.localIP())) { + localDomString = domString; + } + + } catch (Exception e) { + Loggers.SRV_LOG.warn("STATUS-SYNCHRONIZE", "Failed to get domain status from " + ip, e); + } + + } + + result.put("local dom", localDomString); + result.put("diff list", diff); + + return result; + } + + @RequestMapping("/containerNotify") + public String containerNotify(HttpServletRequest request) { + + String type = BaseServlet.required(request, "type"); + String domain = BaseServlet.required(request, "domain"); + String ip = BaseServlet.required(request, "ip"); + String port = BaseServlet.required(request, "port"); + String state = BaseServlet.optional(request, "state", StringUtils.EMPTY); + + Loggers.SRV_LOG.info("CONTAINER_NOTFY", "received notify event, type:" + type + ", domain:" + domain + + ", ip:" + ip + ", port:" + port + ", state:" + state); + + return "ok"; + } + + private JSONObject toPacket(Domain dom) { + + JSONObject pac = new JSONObject(); + + VirtualClusterDomain vDom = (VirtualClusterDomain) dom; + + pac.put("name", vDom.getName()); + + List ips = vDom.allIPs(); + int invalidIPCount = 0; + int ipCount = 0; + for (IpAddress ip : ips) { + if (!ip.isValid()) { + invalidIPCount++; + } + + ipCount++; + } + + pac.put("ipCount", ipCount); + pac.put("invalidIPCount", invalidIPCount); + + pac.put("owners", vDom.getOwners()); + pac.put("token", vDom.getToken()); + pac.put("checkServer", DistroMapper.mapSrvName(vDom.getName())); + + pac.put("protectThreshold", vDom.getProtectThreshold()); + pac.put("checksum", vDom.getChecksum()); + pac.put("useSpecifiedURL", vDom.isUseSpecifiedURL()); + pac.put("enableClientBeat", vDom.getEnableClientBeat()); + + Date date = new Date(vDom.getLastModifiedMillis()); + pac.put("lastModifiedTime", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date)); + pac.put("resetWeight", vDom.getResetWeight()); + pac.put("enableHealthCheck", vDom.getEnableHealthCheck()); + pac.put("enable", vDom.getEnabled()); + + int totalCkRTMillis = 0; + int validCkRTCount = 0; + + JSONArray clusters = new JSONArray(); + + for (Map.Entry entry : vDom.getClusterMap().entrySet()) { + Cluster cluster = entry.getValue(); + + JSONObject clusterPac = new JSONObject(); + clusterPac.put("name", cluster.getName()); + clusterPac.put("healthChecker", cluster.getHealthChecker()); + clusterPac.put("defCkport", cluster.getDefCkport()); + clusterPac.put("defIPPort", cluster.getDefIPPort()); + clusterPac.put("useIPPort4Check", cluster.isUseIPPort4Check()); + clusterPac.put("submask", cluster.getSubmask()); + clusterPac.put("sitegroup", cluster.getSitegroup()); + clusterPac.put("metadatas", cluster.getMetadata()); + + if (cluster.getHealthCheckTask() != null) { + clusterPac.put("ckRTMillis", cluster.getHealthCheckTask().getCheckRTNormalized()); + + // if there is no IP, the check rt doesn't make sense + if (cluster.allIPs().size() > 0) { + totalCkRTMillis += cluster.getHealthCheckTask().getCheckRTNormalized(); + validCkRTCount++; + } + } + + clusters.add(clusterPac); + } + + pac.put("clusters", clusters); + + if (totalCkRTMillis > 0) { + pac.put("avgCkRTMillis", totalCkRTMillis / validCkRTCount); + } else { + pac.put("avgCkRTMillis", 0); + } + + return pac; + } + + public void setDomainsManager(DomainsManager domainsManager) { + this.domainsManager = domainsManager; + } + + public boolean isEnableTrafficSchedule(String agent) { + ClientInfo clientInfo = new ClientInfo(agent); + + if (clientInfo.type == ClientInfo.ClientType.C + && clientInfo.version.compareTo(VersionUtil.parseVersion(Switch.getTrafficSchedulingCVersion())) >= 0) { + return true; + } + + if (clientInfo.type == ClientInfo.ClientType.JAVA + && clientInfo.version.compareTo(VersionUtil.parseVersion(Switch.getTrafficSchedulingJavaVersion())) >= 0) { + return true; + } + + if (clientInfo.type == ClientInfo.ClientType.DNS + && clientInfo.version.compareTo(VersionUtil.parseVersion(Switch.getTrafficSchedulingPythonVersion())) >= 0) { + return true; + } + + if (clientInfo.type == ClientInfo.ClientType.TENGINE + && clientInfo.version.compareTo(VersionUtil.parseVersion(Switch.getTrafficSchedulingTengineVersion())) >= 0) { + return true; + } + + return false; + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/web/AuthFilter.java b/naming/src/main/java/com/alibaba/nacos/naming/web/AuthFilter.java new file mode 100644 index 00000000000..2f769ab1083 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/web/AuthFilter.java @@ -0,0 +1,110 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.web; + +import com.alibaba.nacos.naming.acl.AuthChecker; +import com.alibaba.nacos.naming.misc.Switch; +import com.alibaba.nacos.naming.misc.UtilsAndCommons; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.URI; +import java.security.AccessControlException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * @author dungu.zpf + */ + +public class AuthFilter implements Filter { + + @Autowired + private AuthChecker authChecker; + + private static ConcurrentMap methodCache = new + ConcurrentHashMap(); + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, + FilterChain filterChain) throws IOException, ServletException { + + HttpServletRequest req = (HttpServletRequest) servletRequest; + HttpServletResponse resp = (HttpServletResponse) servletResponse; + + try { + String path = new URI(req.getRequestURI()).getPath(); + String target = getMethodName(path); + + Method method = methodCache.get(target); + + if (method == null) { + if (path.contains(UtilsAndCommons.NACOS_NAMING_RAFT_CONTEXT)) { + method = RaftCommands.class.getMethod(target, HttpServletRequest.class, HttpServletResponse.class); + } else { + method = ApiCommands.class.getMethod(target, HttpServletRequest.class); + } + methodCache.put(target, method); + } + + if (method.isAnnotationPresent(NeedAuth.class) && !Switch.isEnableAuthentication()) { + + if (path.contains(UtilsAndCommons.NACOS_NAMING_RAFT_CONTEXT)) { + authChecker.doRaftAuth(req); + } else { + authChecker.doAuth(req.getParameterMap(), req); + } + } + + } catch (AccessControlException e) { + resp.sendError(HttpServletResponse.SC_FORBIDDEN, "access denied: " + UtilsAndCommons.getAllExceptionMsg(e)); + return; + } catch (NoSuchMethodException e) { + resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, "no such api"); + return; + } catch (Exception e) { + resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + "Server failed," + UtilsAndCommons.getAllExceptionMsg(e)); + return; + } + filterChain.doFilter(req, resp); + } + + @Override + public void destroy() { + + } + + static protected String getMethodName(String path) throws Exception { + String target = path.substring(path.lastIndexOf("/") + 1).trim(); + + if (StringUtils.isEmpty(target)) { + throw new IllegalArgumentException("URL target required"); + } + + return target; + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/web/BaseServlet.java b/naming/src/main/java/com/alibaba/nacos/naming/web/BaseServlet.java new file mode 100644 index 00000000000..1c71d56e6b3 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/web/BaseServlet.java @@ -0,0 +1,71 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.web; + + +import org.apache.commons.lang3.StringUtils; + +import javax.servlet.http.HttpServletRequest; +import java.io.UnsupportedEncodingException; + +/** + * @author nacos + */ +public class BaseServlet { + + public static String required(HttpServletRequest req, String key) { + String value = req.getParameter(key); + if (StringUtils.isEmpty(value)) { + throw new IllegalArgumentException("Param '" + key + "' is required."); + } + + String encoding = req.getParameter("encoding"); + if (!StringUtils.isEmpty(encoding)) { + try { + value = new String(value.getBytes("UTF-8"), encoding); + } catch (UnsupportedEncodingException ignore) { + } + } + + return value.trim(); + } + + public static String optional(HttpServletRequest req, String key, String defaultValue) { + + if (!req.getParameterMap().containsKey(key) || req.getParameterMap().get(key)[0] == null) { + return defaultValue; + } + + String value = req.getParameter(key); + + String encoding = req.getParameter("encoding"); + if (!StringUtils.isEmpty(encoding)) { + try { + value = new String(value.getBytes("UTF-8"), encoding); + } catch (UnsupportedEncodingException ignore) { + } + } + + return value.trim(); + } + + public static String getAcceptEncoding(HttpServletRequest req) { + String encode = StringUtils.defaultIfEmpty(req.getHeader("Accept-Charset"), "UTF-8"); + encode = encode.contains(",") ? encode.substring(0, encode.indexOf(",")) : encode; + return encode.contains(";") ? encode.substring(0, encode.indexOf(";")) : encode; + } + +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/web/DistroFilter.java b/naming/src/main/java/com/alibaba/nacos/naming/web/DistroFilter.java new file mode 100644 index 00000000000..421e2405be5 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/web/DistroFilter.java @@ -0,0 +1,111 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.web; + +import com.alibaba.nacos.naming.core.DistroMapper; +import com.alibaba.nacos.naming.misc.Loggers; +import com.alibaba.nacos.naming.misc.Switch; +import com.alibaba.nacos.naming.misc.UtilsAndCommons; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.apache.commons.lang3.StringUtils; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Map; + +/** + * @author nacos + */ +public class DistroFilter implements Filter { + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + @SuppressFBWarnings("HRS_REQUEST_PARAMETER_TO_HTTP_HEADER") + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + HttpServletRequest req = (HttpServletRequest) servletRequest; + HttpServletResponse resp = (HttpServletResponse) servletResponse; + + String urlString = req.getRequestURI() + "?" + req.getQueryString(); + Map limitedUrlMap = Switch.getLimitedUrlMap(); + + if (limitedUrlMap != null && limitedUrlMap.size() > 0) { + for (Map.Entry entry : limitedUrlMap.entrySet()) { + String limitedUrl = entry.getKey(); + if (StringUtils.startsWith(urlString, limitedUrl)) { + resp.setStatus(entry.getValue()); + return; + } + } + } + + if (!Switch.isDistroEnabled()) { + filterChain.doFilter(req, resp); + return; + } + + if (!canDistro(urlString)) { + filterChain.doFilter(req, resp); + return; + } + + String redirect = req.getParameter("redirect"); + String dom = req.getParameter("domainString"); + String targetIP = req.getParameter("targetIP"); + if (StringUtils.isEmpty(dom)) { + dom = req.getParameter("dom"); + } + + if (StringUtils.isEmpty(dom)) { + filterChain.doFilter(req, resp); + return; + } + + if (StringUtils.isEmpty(redirect) && StringUtils.isEmpty(targetIP)) { + if (!DistroMapper.responsible(dom)) { + + String url = "http://" + DistroMapper.mapSrv(dom) + ":" + req.getServerPort() + + req.getRequestURI() + "?" + req.getQueryString(); + try { + resp.sendRedirect(url); + } catch (Exception ignore) { + Loggers.SRV_LOG.warn("DISTRO-FILTER", "request failed: " + url); + } + } + } + + filterChain.doFilter(req, resp); + } + + @Override + public void destroy() { + + } + + public boolean canDistro(String urlString) { + + if (urlString.startsWith(UtilsAndCommons.NACOS_NAMING_CONTEXT + UtilsAndCommons.API_DOM_SERVE_STATUS)) { + return false; + } + + return urlString.startsWith(UtilsAndCommons.NACOS_NAMING_CONTEXT + UtilsAndCommons.API_IP_FOR_DOM) || + urlString.startsWith(UtilsAndCommons.NACOS_NAMING_CONTEXT + UtilsAndCommons.API_DOM); + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/web/MockHttpRequest.java b/naming/src/main/java/com/alibaba/nacos/naming/web/MockHttpRequest.java new file mode 100644 index 00000000000..ffee2f1a891 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/web/MockHttpRequest.java @@ -0,0 +1,390 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.web; + +import javax.servlet.*; +import javax.servlet.http.*; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.Principal; +import java.util.*; + +/** + * @author dungu.zpf + */ +public class MockHttpRequest implements HttpServletRequest { + + private Map params; + + public static MockHttpRequest buildRequest(Map params) { + + MockHttpRequest request = new MockHttpRequest(); + request.params = params; + request.params.put("encoding", new String[]{"UTF-8"}); + + return request; + } + + public void addParameter(String key, String value) { + params.put(key, new String[]{value}); + } + + @Override + public String getAuthType() { + return null; + } + + @Override + public Cookie[] getCookies() { + return new Cookie[0]; + } + + @Override + public long getDateHeader(String s) { + return 0; + } + + @Override + public String getHeader(String s) { + return null; + } + + @Override + public Enumeration getHeaders(String s) { + return null; + } + + @Override + public Enumeration getHeaderNames() { + return null; + } + + @Override + public int getIntHeader(String s) { + return 0; + } + + @Override + public String getMethod() { + return null; + } + + @Override + public String getPathInfo() { + return null; + } + + @Override + public String getPathTranslated() { + return null; + } + + @Override + public String getContextPath() { + return null; + } + + @Override + public String getQueryString() { + return null; + } + + @Override + public String getRemoteUser() { + return null; + } + + @Override + public boolean isUserInRole(String s) { + return false; + } + + @Override + public Principal getUserPrincipal() { + return null; + } + + @Override + public String getRequestedSessionId() { + return null; + } + + @Override + public String getRequestURI() { + return null; + } + + @Override + public StringBuffer getRequestURL() { + return null; + } + + @Override + public String getServletPath() { + return null; + } + + @Override + public HttpSession getSession(boolean b) { + return null; + } + + @Override + public HttpSession getSession() { + return null; + } + + @Override + public String changeSessionId() { + return null; + } + + @Override + public boolean isRequestedSessionIdValid() { + return false; + } + + @Override + public boolean isRequestedSessionIdFromCookie() { + return false; + } + + @Override + public boolean isRequestedSessionIdFromURL() { + return false; + } + + @Override + public boolean isRequestedSessionIdFromUrl() { + return false; + } + + @Override + public boolean authenticate(HttpServletResponse httpServletResponse) throws IOException, ServletException { + return false; + } + + @Override + public void login(String s, String s1) throws ServletException { + + } + + @Override + public void logout() throws ServletException { + + } + + @Override + public Collection getParts() throws IOException, ServletException { + return null; + } + + @Override + public Part getPart(String s) throws IOException, ServletException { + return null; + } + + @Override + public T upgrade(Class aClass) throws IOException, ServletException { + return null; + } + + @Override + public Object getAttribute(String s) { + return null; + } + + @Override + public Enumeration getAttributeNames() { + return null; + } + + @Override + public String getCharacterEncoding() { + return null; + } + + @Override + public void setCharacterEncoding(String s) throws UnsupportedEncodingException { + + } + + @Override + public int getContentLength() { + return 0; + } + + @Override + public long getContentLengthLong() { + return 0; + } + + @Override + public String getContentType() { + return null; + } + + @Override + public ServletInputStream getInputStream() throws IOException { + return null; + } + + @Override + public String getParameter(String s) { + return params.get(s)[0]; + } + + @Override + public Enumeration getParameterNames() { + return new Vector<>(params.keySet()).elements(); + } + + @Override + public String[] getParameterValues(String s) { + return params.get(s); + } + + @Override + public Map getParameterMap() { + return params; + } + + @Override + public String getProtocol() { + return null; + } + + @Override + public String getScheme() { + return null; + } + + @Override + public String getServerName() { + return null; + } + + @Override + public int getServerPort() { + return 0; + } + + @Override + public BufferedReader getReader() throws IOException { + return null; + } + + @Override + public String getRemoteAddr() { + return null; + } + + @Override + public String getRemoteHost() { + return null; + } + + @Override + public void setAttribute(String s, Object o) { + + } + + @Override + public void removeAttribute(String s) { + + } + + @Override + public Locale getLocale() { + return null; + } + + @Override + public Enumeration getLocales() { + return null; + } + + @Override + public boolean isSecure() { + return false; + } + + @Override + public RequestDispatcher getRequestDispatcher(String s) { + return null; + } + + @Override + public String getRealPath(String s) { + return null; + } + + @Override + public int getRemotePort() { + return 0; + } + + @Override + public String getLocalName() { + return null; + } + + @Override + public String getLocalAddr() { + return null; + } + + @Override + public int getLocalPort() { + return 0; + } + + @Override + public ServletContext getServletContext() { + return null; + } + + @Override + public AsyncContext startAsync() throws IllegalStateException { + return null; + } + + @Override + public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException { + return null; + } + + @Override + public boolean isAsyncStarted() { + return false; + } + + @Override + public boolean isAsyncSupported() { + return false; + } + + @Override + public AsyncContext getAsyncContext() { + return null; + } + + @Override + public DispatcherType getDispatcherType() { + return null; + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/web/NamingConfig.java b/naming/src/main/java/com/alibaba/nacos/naming/web/NamingConfig.java new file mode 100644 index 00000000000..e8b04a13abd --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/web/NamingConfig.java @@ -0,0 +1,63 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.web; + +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.servlet.Filter; + +/** + * @author dungu.zpf + */ + +@Configuration +public class NamingConfig { + + @Bean + public FilterRegistrationBean distroFilterRegistration() { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setFilter(distroFilter()); + registration.addUrlPatterns("/*"); + registration.setName("distroFilter"); + registration.setOrder(6); + + return registration; + } + + @Bean + public FilterRegistrationBean authFilterRegistration() { + FilterRegistrationBean registration = new FilterRegistrationBean(); + + registration.setFilter(authFilter()); + registration.addUrlPatterns("/api/*", "/raft/*"); + registration.setName("authFilter"); + registration.setOrder(5); + + return registration; + } + + @Bean + public Filter distroFilter() { + return new DistroFilter(); + } + + @Bean + public Filter authFilter() { + return new AuthFilter(); + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/web/NeedAuth.java b/naming/src/main/java/com/alibaba/nacos/naming/web/NeedAuth.java new file mode 100644 index 00000000000..e8c9867d066 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/web/NeedAuth.java @@ -0,0 +1,25 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.web; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Innovated By: Xuanyin.zy + */ +public @Retention(RetentionPolicy.RUNTIME) @interface NeedAuth { +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/web/RaftCommands.java b/naming/src/main/java/com/alibaba/nacos/naming/web/RaftCommands.java new file mode 100644 index 00000000000..d09715cf000 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/web/RaftCommands.java @@ -0,0 +1,246 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.web; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.nacos.common.util.IoUtils; +import com.alibaba.nacos.naming.core.DomainsManager; +import com.alibaba.nacos.naming.core.VirtualClusterDomain; +import com.alibaba.nacos.naming.misc.NetUtils; +import com.alibaba.nacos.naming.misc.UtilsAndCommons; +import com.alibaba.nacos.naming.raft.*; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * @author nacos + */ +@RestController +@RequestMapping(UtilsAndCommons.NACOS_NAMING_CONTEXT + "/raft") +public class RaftCommands { + + @Autowired + protected DomainsManager domainsManager; + + @NeedAuth + @RequestMapping("/vote") + public JSONObject vote(HttpServletRequest request, HttpServletResponse response) throws Exception { + + RaftPeer peer = RaftCore.MasterElection.receivedVote( + JSON.parseObject(BaseServlet.required(request, "vote"), RaftPeer.class)); + + return JSON.parseObject(JSON.toJSONString(peer)); + } + + @NeedAuth + @RequestMapping("/beat") + public JSONObject beat(HttpServletRequest request, HttpServletResponse response) throws Exception { + + String entity = new String(IoUtils.tryDecompress(request.getInputStream()), "UTF-8"); + + String value = Arrays.asList(entity).toArray(new String[1])[0]; + + JSONObject json = JSON.parseObject(value); + JSONObject beat = JSON.parseObject(json.getString("beat")); + + RaftPeer peer = RaftCore.HeartBeat.receivedBeat(beat); + + return JSON.parseObject(JSON.toJSONString(peer)); + } + + @NeedAuth + @RequestMapping("/getPeer") + public JSONObject getPeer(HttpServletRequest request, HttpServletResponse response) { + List peers = RaftCore.getPeers(); + RaftPeer peer = null; + + for (RaftPeer peer1 : peers) { + if (StringUtils.equals(peer1.ip, NetUtils.localIP())) { + peer = peer1; + } + } + + if (peer == null) { + peer = new RaftPeer(); + peer.ip = NetUtils.localIP(); + } + + return JSON.parseObject(JSON.toJSONString(peer)); + } + + @NeedAuth + @RequestMapping("/reloadDatum") + public String reloadDatum(HttpServletRequest request, HttpServletResponse response) throws Exception { + String key = BaseServlet.required(request, "key"); + RaftStore.load(key); + return "ok"; + } + + @NeedAuth + @RequestMapping("/publish") + public String publish(HttpServletRequest request, HttpServletResponse response) throws Exception { + + response.setHeader("Content-Type", "application/json; charset=" + getAcceptEncoding(request)); + response.setHeader("Cache-Control", "no-cache"); + response.setHeader("Content-Encode", "gzip"); + + String entity = IoUtils.toString(request.getInputStream(), "UTF-8"); + + String value = Arrays.asList(entity).toArray(new String[1])[0]; + JSONObject json = JSON.parseObject(value); + + RaftCore.signalPublish(json.getString("key"), json.getString("value")); + + return "ok"; + } + + @NeedAuth + @RequestMapping("/unSafePublish") + public String unSafePublish(HttpServletRequest request, HttpServletResponse response) throws Exception { + + response.setHeader("Content-Type", "application/json; charset=" + getAcceptEncoding(request)); + response.setHeader("Cache-Control", "no-cache"); + response.setHeader("Content-Encode", "gzip"); + + String entity = IoUtils.toString(request.getInputStream(), "UTF-8"); + + String value = Arrays.asList(entity).toArray(new String[1])[0]; + JSONObject json = JSON.parseObject(value); + + RaftCore.unsafePublish(json.getString("key"), json.getString("value")); + return "ok"; + } + + @NeedAuth + @RequestMapping("/delete") + public String delete(HttpServletRequest request, HttpServletResponse response) throws Exception { + + response.setHeader("Content-Type", "application/json; charset=" + getAcceptEncoding(request)); + response.setHeader("Cache-Control", "no-cache"); + response.setHeader("Content-Encode", "gzip"); + RaftCore.signalDelete(BaseServlet.required(request, "key")); + return "ok"; + } + + @NeedAuth + @RequestMapping("/get") + public String get(HttpServletRequest request, HttpServletResponse response) throws Exception { + + response.setHeader("Content-Type", "application/json; charset=" + getAcceptEncoding(request)); + response.setHeader("Cache-Control", "no-cache"); + response.setHeader("Content-Encode", "gzip"); + String keysString = BaseServlet.required(request, "keys"); + String[] keys = keysString.split(","); + List datums = new ArrayList(); + + for (String key : keys) { + Datum datum = RaftCore.getDatum(key); + datums.add(datum); + } + + return JSON.toJSONString(datums); + } + + @RequestMapping("/state") + public JSONObject state(HttpServletRequest request, HttpServletResponse response) throws Exception { + + response.setHeader("Content-Type", "application/json; charset=" + getAcceptEncoding(request)); + response.setHeader("Cache-Control", "no-cache"); + response.setHeader("Content-Encode", "gzip"); + + JSONObject result = new JSONObject(); + result.put("doms", domainsManager.getRaftDomMap().size()); + result.put("peers", RaftCore.getPeers()); + + return result; + } + + @NeedAuth + @RequestMapping("/onPublish") + public String onPublish(HttpServletRequest request, HttpServletResponse response) throws Exception { + + response.setHeader("Content-Type", "application/json; charset=" + getAcceptEncoding(request)); + response.setHeader("Cache-Control", "no-cache"); + response.setHeader("Content-Encode", "gzip"); + + String entity = IoUtils.toString(request.getInputStream(), "UTF-8"); + + String value = Arrays.asList(entity).toArray(new String[1])[0]; + JSONObject jsonObject = JSON.parseObject(value); + RaftCore.onPublish(jsonObject); + return "ok"; + } + + @NeedAuth + @RequestMapping("/onDelete") + public String onDelete(HttpServletRequest request, HttpServletResponse response) throws Exception { + + response.setHeader("Content-Type", "application/json; charset=" + getAcceptEncoding(request)); + response.setHeader("Cache-Control", "no-cache"); + response.setHeader("Content-Encode", "gzip"); + + String entity = IoUtils.toString(request.getInputStream(), "UTF-8"); + + String value = Arrays.asList(entity).toArray(new String[1])[0]; + RaftCore.onDelete(JSON.parseObject(value)); + return "ok"; + } + + public void setDomainsManager(DomainsManager domainsManager) { + this.domainsManager = domainsManager; + } + + @RequestMapping("/getLeader") + public JSONObject getLeader(HttpServletRequest request, HttpServletResponse response) { + + JSONObject result = new JSONObject(); + result.put("leader", JSONObject.toJSONString(RaftCore.getLeader())); + return result; + } + + @RequestMapping("/getAllListeners") + public JSONObject getAllListeners(HttpServletRequest request, HttpServletResponse response) { + + JSONObject result = new JSONObject(); + List listeners = RaftCore.getListeners(); + + JSONArray listenerArray = new JSONArray(); + for (RaftListener listener : listeners) { + if (listener instanceof VirtualClusterDomain) { + listenerArray.add(((VirtualClusterDomain) listener).getName()); + } + } + result.put("listeners", listenerArray); + + return result; + } + + public static String getAcceptEncoding(HttpServletRequest req) { + String encode = StringUtils.defaultIfEmpty(req.getHeader("Accept-Charset"), "UTF-8"); + encode = encode.contains(",") ? encode.substring(0, encode.indexOf(",")) : encode; + return encode.contains(";") ? encode.substring(0, encode.indexOf(";")) : encode; + } +} diff --git a/naming/src/main/resources/application.properties b/naming/src/main/resources/application.properties new file mode 100644 index 00000000000..0342d6dde0c --- /dev/null +++ b/naming/src/main/resources/application.properties @@ -0,0 +1,10 @@ +server.port=8080 +server.servlet.context-path=/nacos + + +# Number of ms to wait before throwing an exception if no connection is available. +spring.datasource.max-wait=10000 +# Maximum number of active connections that can be allocated from this pool at the same time. +spring.datasource.max-active=15 +## Validate the connection before borrowing it from the pool. +#spring.datasource.test-on-borrow=true diff --git a/naming/src/main/resources/banner.txt b/naming/src/main/resources/banner.txt new file mode 100644 index 00000000000..4ebabe6fa06 --- /dev/null +++ b/naming/src/main/resources/banner.txt @@ -0,0 +1,15 @@ + + ,--. + ,--.'| + ,--,: : | +,`--.'`| ' : ,---. +| : : | | ' ,'\ .--.--. +: | \ | : ,--.--. ,---. / / | / / ' +| : ' '; | / \ / \. ; ,. :| : /`./ +' ' ;. ;.--. .-. | / / '' | |: :| : ;_ +| | | \ | \__\/: . .. ' / ' | .; : \ \ `. +' : | ; .' ," .--.; |' ; :__| : | `----. \ +| | '`--' / / ,. |' | '.'|\ \ / / /`--' / +' : | ; : .' \ : : `----' '--'. / +; |.' | , .-./\ \ / `--'---' +'---' `--`---' `----' diff --git a/naming/src/main/resources/naming-logback.xml b/naming/src/main/resources/naming-logback.xml new file mode 100644 index 00000000000..641b92745ed --- /dev/null +++ b/naming/src/main/resources/naming-logback.xml @@ -0,0 +1,247 @@ + + + + + + ${user.home}/nacos/logs/naming-server.log + true + + ${user.home}/nacos/logs/naming-server.log.%d{yyyy-MM-dd}.%i + 2GB + 15 + 7GB + true + + + %date %level %msg%n%n + UTF-8 + + + + ${user.home}/nacos/logs/naming-raft.log + true + + ${user.home}/nacos/logs/naming-raft.log.%d{yyyy-MM-dd}.%i + 20MB + 15 + 128MB + true + + + %date %level %msg%n%n + UTF-8 + + + + ${user.home}/nacos/logs/naming-event.log + true + + ${user.home}/nacos/logs/naming-event.log.%d{yyyy-MM-dd}.%i + 20MB + 15 + 128MB + true + + + %date %level %msg%n%n + UTF-8 + + + + ${user.home}/nacos/logs/naming-push.log + true + + ${user.home}/nacos/logs/naming-push.log.%d{yyyy-MM-dd}.%i + 20MB + 15 + 128MB + true + + + %date %level %msg%n%n + UTF-8 + + + + ${user.home}/nacos/logs/naming-rt.log + true + + ${user.home}/nacos/logs/naming-rt.log.%d{yyyy-MM-dd}.%i + 1GB + 15 + 3GB + true + + + %msg%n + UTF-8 + + + + + ${user.home}/nacos/logs/naming-performance.log + true + + ${user.home}/nacos/logs/naming-performance.log.%d{yyyy-MM-dd}.%i + 50MB + 15 + 512MB + true + + + %date %level %msg%n%n + UTF-8 + + + + + ${user.home}/nacos/logs/naming-router.log + true + + ${user.home}/nacos/logs/naming-router.log.%d{yyyy-MM-dd}.%i + 2GB + 15 + 7GB + true + + + %date|%msg%n + UTF-8 + + + + + ${user.home}/nacos/logs/naming-cache.log + true + + ${user.home}/nacos/logs/naming-cache.log.%d{yyyy-MM-dd}.%i + 1GB + 15 + 3GB + true + + + %date|%msg%n + UTF-8 + + + + + ${user.home}/nacos/logs/naming-device.log + true + + ${user.home}/nacos/logs/naming-device.log.%d{yyyy-MM-dd}.%i + 2GB + 15 + 7GB + true + + + %date|%msg%n + UTF-8 + + + + + ${user.home}/nacos/logs/naming-tag.log + true + + ${user.home}/nacos/logs/naming-tag.log.%d{yyyy-MM-dd}.%i + 1GB + 15 + 3GB + true + + + %date %level %msg%n%n + UTF-8 + + + + + ${user.home}/nacos/logs/naming-debug.log + true + + ${user.home}/nacos/logs/naming-debug.log.%d{yyyy-MM-dd}.%i + 20MB + 15 + 128MB + true + + + %date %level %msg%n%n + UTF-8 + + + + + ${user.home}/nacos/logs/nacos.log + true + + ${user.home}/nacos/logs/nacos.log.%d{yyyy-MM-dd}.%i + 50MB + 15 + 512MB + true + + + %date %level %msg%n%n + UTF-8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/naming/src/test/java/com/alibaba/nacos/naming/BaseTest.java b/naming/src/test/java/com/alibaba/nacos/naming/BaseTest.java new file mode 100644 index 00000000000..dd05ae64cb2 --- /dev/null +++ b/naming/src/test/java/com/alibaba/nacos/naming/BaseTest.java @@ -0,0 +1,50 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming; + +import com.alibaba.nacos.naming.core.DomainsManager; +import com.alibaba.nacos.naming.misc.NetUtils; +import com.alibaba.nacos.naming.raft.PeerSet; +import com.alibaba.nacos.naming.raft.RaftCore; +import com.alibaba.nacos.naming.raft.RaftPeer; +import org.junit.Before; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +/** + * @author dungu.zpf + */ +public class BaseTest { + + @Mock + public DomainsManager domainsManager; + + @Mock + public PeerSet peerSet; + + @Before + public void before() { + MockitoAnnotations.initMocks(this); + + RaftPeer peer = new RaftPeer(); + peer.ip = NetUtils.localIP(); + RaftCore.setPeerSet(peerSet); + Mockito.when(peerSet.local()).thenReturn(peer); + Mockito.when(peerSet.getLeader()).thenReturn(peer); + Mockito.when(peerSet.isLeader(NetUtils.localIP())).thenReturn(true); + } +} diff --git a/naming/src/test/java/com/alibaba/nacos/naming/controllers/InstanceControllerTest.java b/naming/src/test/java/com/alibaba/nacos/naming/controllers/InstanceControllerTest.java new file mode 100644 index 00000000000..78d6968d389 --- /dev/null +++ b/naming/src/test/java/com/alibaba/nacos/naming/controllers/InstanceControllerTest.java @@ -0,0 +1,154 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.controllers; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.nacos.naming.BaseTest; +import com.alibaba.nacos.naming.core.Cluster; +import com.alibaba.nacos.naming.core.IpAddress; +import com.alibaba.nacos.naming.core.VirtualClusterDomain; +import com.alibaba.nacos.naming.misc.UtilsAndCommons; +import com.alibaba.nacos.naming.raft.PeerSet; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.springframework.mock.web.MockServletContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +/** + * @author dungu.zpf + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = MockServletContext.class) +@WebAppConfiguration +public class InstanceControllerTest extends BaseTest { + + @InjectMocks + private InstanceController instanceController; + + @Mock + private PeerSet peerSet; + + private MockMvc mockmvc; + + @Before + public void before() { + super.before(); + mockmvc = MockMvcBuilders.standaloneSetup(instanceController).build(); + } + + @Test + public void registerInstance() throws Exception { + + VirtualClusterDomain domain = new VirtualClusterDomain(); + domain.setName("nacos.test.1"); + + Cluster cluster = new Cluster(); + cluster.setName(UtilsAndCommons.DEFAULT_CLUSTER_NAME); + cluster.setDom(domain); + domain.addCluster(cluster); + + IpAddress ipAddress = new IpAddress(); + ipAddress.setIp("1.1.1.1"); + ipAddress.setPort(9999); + List ipList = new ArrayList<>(); + ipList.add(ipAddress); + domain.updateIPs(ipList, false); + + Mockito.when(domainsManager.getDomain("nacos.test.1")).thenReturn(domain); + + Mockito.when(domainsManager.addLock("nacos.test.1")).thenReturn(new ReentrantLock()); + + MockHttpServletRequestBuilder builder = + MockMvcRequestBuilders.put("/naming/instance") + .param("serviceName", "nacos.test.1") + .param("ip", "1.1.1.1") + .param("port", "9999"); + String actualValue = mockmvc.perform(builder).andReturn().getResponse().getContentAsString(); + + Assert.assertEquals("ok", actualValue); + } + + @Test + public void deregisterInstance() throws Exception { + + MockHttpServletRequestBuilder builder = + MockMvcRequestBuilders.delete("/naming/instance") + .param("serviceName", "nacos.test.1") + .param("ip", "1.1.1.1") + .param("port", "9999") + .param("clusterName", UtilsAndCommons.DEFAULT_CLUSTER_NAME); + String actualValue = mockmvc.perform(builder).andReturn().getResponse().getContentAsString(); + + Assert.assertEquals("ok", actualValue); + } + + @Test + public void getInstances() throws Exception { + + VirtualClusterDomain domain = new VirtualClusterDomain(); + domain.setName("nacos.test.1"); + + Cluster cluster = new Cluster(); + cluster.setName(UtilsAndCommons.DEFAULT_CLUSTER_NAME); + cluster.setDom(domain); + domain.addCluster(cluster); + + IpAddress ipAddress = new IpAddress(); + ipAddress.setIp("10.10.10.10"); + ipAddress.setPort(8888); + ipAddress.setWeight(2.0); + List ipList = new ArrayList<>(); + ipList.add(ipAddress); + domain.updateIPs(ipList, false); + + Mockito.when(domainsManager.getDomain("nacos.test.1")).thenReturn(domain); + + MockHttpServletRequestBuilder builder = + MockMvcRequestBuilders.get("/naming/instances") + .param("serviceName", "nacos.test.1"); + String actualValue = mockmvc.perform(builder).andReturn().getResponse().getContentAsString(); + JSONObject result = JSON.parseObject(actualValue); + + Assert.assertEquals("nacos.test.1", result.getString("dom")); + JSONArray hosts = result.getJSONArray("hosts"); + Assert.assertTrue(hosts != null); + Assert.assertNotNull(hosts); + Assert.assertEquals(hosts.size(), 1); + + JSONObject host = hosts.getJSONObject(0); + Assert.assertNotNull(host); + Assert.assertEquals("10.10.10.10", host.getString("ip")); + Assert.assertEquals(8888, host.getIntValue("port")); + Assert.assertEquals(2.0, host.getDoubleValue("weight"), 0.001); + } +} diff --git a/naming/src/test/java/com/alibaba/nacos/naming/core/ClusterTest.java b/naming/src/test/java/com/alibaba/nacos/naming/core/ClusterTest.java new file mode 100644 index 00000000000..2ae9f971277 --- /dev/null +++ b/naming/src/test/java/com/alibaba/nacos/naming/core/ClusterTest.java @@ -0,0 +1,100 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.core; + +import com.alibaba.nacos.naming.healthcheck.AbstractHealthCheckConfig; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author dungu.zpf + */ +public class ClusterTest { + + private Cluster cluster; + + @Before + public void before() { + + VirtualClusterDomain domain = new VirtualClusterDomain(); + domain.setName("nacos.domain.1"); + + cluster = new Cluster(); + cluster.setName("nacos-cluster-1"); + cluster.setDom(domain); + cluster.setDefCkport(80); + cluster.setDefIPPort(8080); + } + + + @Test + public void updateCluster() { + + Cluster newCluster = new Cluster(); + newCluster.setDefCkport(8888); + newCluster.setDefIPPort(9999); + AbstractHealthCheckConfig.Http healthCheckConfig = new AbstractHealthCheckConfig.Http(); + healthCheckConfig.setPath("/nacos-path-1"); + healthCheckConfig.setExpectedResponseCode(500); + healthCheckConfig.setHeaders("Client-Version:nacos-test-1"); + newCluster.setHealthChecker(healthCheckConfig); + + VirtualClusterDomain domain = new VirtualClusterDomain(); + domain.setName("nacos.domain.2"); + + newCluster.setDom(domain); + + cluster.update(newCluster); + + Assert.assertEquals(8888, cluster.getDefCkport()); + Assert.assertEquals(9999, cluster.getDefIPPort()); + Assert.assertTrue(cluster.getHealthChecker() instanceof AbstractHealthCheckConfig.Http); + AbstractHealthCheckConfig.Http httpHealthCheck = (AbstractHealthCheckConfig.Http)(cluster.getHealthChecker()); + Assert.assertEquals("/nacos-path-1", httpHealthCheck.getPath()); + Assert.assertEquals(500, httpHealthCheck.getExpectedResponseCode()); + Assert.assertEquals("Client-Version:nacos-test-1", httpHealthCheck.getHeaders()); + } + + @Test + public void updateIps() { + + IpAddress ipAddress1 = new IpAddress(); + ipAddress1.setIp("1.1.1.1"); + ipAddress1.setPort(1234); + + IpAddress ipAddress2 = new IpAddress(); + ipAddress2.setIp("1.1.1.1"); + ipAddress2.setPort(2345); + + List list = new ArrayList<>(); + list.add(ipAddress1); + list.add(ipAddress2); + + cluster.updateIPs(list, false); + + List ips = cluster.allIPs(); + Assert.assertNotNull(ips); + Assert.assertEquals(2, ips.size()); + Assert.assertEquals("1.1.1.1", ips.get(0).getIp()); + Assert.assertEquals(1234, ips.get(0).getPort()); + Assert.assertEquals("1.1.1.1", ips.get(1).getIp()); + Assert.assertEquals(2345, ips.get(1).getPort()); + } +} diff --git a/naming/src/test/java/com/alibaba/nacos/naming/core/DomainTest.java b/naming/src/test/java/com/alibaba/nacos/naming/core/DomainTest.java new file mode 100644 index 00000000000..cf4d4efaa43 --- /dev/null +++ b/naming/src/test/java/com/alibaba/nacos/naming/core/DomainTest.java @@ -0,0 +1,96 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.core; + +import com.alibaba.fastjson.JSON; +import com.alibaba.nacos.naming.misc.UtilsAndCommons; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @author dungu.zpf + */ +public class DomainTest { + + private VirtualClusterDomain domain; + + @Before + public void before() { + domain = new VirtualClusterDomain(); + domain.setName("nacos.domain.1"); + Cluster cluster = new Cluster(); + cluster.setName(UtilsAndCommons.DEFAULT_CLUSTER_NAME); + cluster.setDom(domain); + domain.addCluster(cluster); + } + + @Test + public void updateDomain() { + + VirtualClusterDomain newDomain = new VirtualClusterDomain(); + newDomain.setName("nacos.domain.1"); + newDomain.setEnableClientBeat(false); + newDomain.setEnableHealthCheck(false); + newDomain.setProtectThreshold(0.7f); + Cluster cluster = new Cluster(); + cluster.setName(UtilsAndCommons.DEFAULT_CLUSTER_NAME); + cluster.setDom(newDomain); + newDomain.addCluster(cluster); + + domain.update(newDomain); + + Assert.assertEquals(false, domain.getEnableClientBeat()); + Assert.assertEquals(false, domain.getEnableHealthCheck()); + Assert.assertEquals(0.7f, domain.getProtectThreshold(), 0.0001f); + } + + @Test + public void addCluster() { + Cluster cluster = new Cluster(); + cluster.setName("nacos-cluster-1"); + + domain.addCluster(cluster); + + Map clusterMap = domain.getClusterMap(); + Assert.assertNotNull(clusterMap); + Assert.assertEquals(2, clusterMap.size()); + Assert.assertTrue(clusterMap.containsKey("nacos-cluster-1")); + } + + @Test + public void updateIps() throws Exception { + + IpAddress ipAddress = new IpAddress(); + ipAddress.setIp("1.1.1.1"); + ipAddress.setPort(1234); + List list = new ArrayList<>(); + list.add(ipAddress); + + domain.onChange("iplist", JSON.toJSONString(list)); + + List ips = domain.allIPs(); + + Assert.assertNotNull(ips); + Assert.assertEquals(1, ips.size()); + Assert.assertEquals("1.1.1.1", ips.get(0).getIp()); + Assert.assertEquals(1234, ips.get(0).getPort()); + } +} diff --git a/naming/src/test/java/com/alibaba/nacos/naming/core/DomainsManagerTest.java b/naming/src/test/java/com/alibaba/nacos/naming/core/DomainsManagerTest.java new file mode 100644 index 00000000000..ed28b92e3d5 --- /dev/null +++ b/naming/src/test/java/com/alibaba/nacos/naming/core/DomainsManagerTest.java @@ -0,0 +1,80 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.core; + +import com.alibaba.nacos.naming.BaseTest; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.mock.web.MockServletContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author dungu.zpf + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = MockServletContext.class) +@WebAppConfiguration +public class DomainsManagerTest extends BaseTest { + + private DomainsManager domainsManager; + + @Before + public void before() { + super.before(); + domainsManager = new DomainsManager(); + } + + @Test + public void easyRemoveDom() throws Exception { + domainsManager.easyRemoveDom("nacos.test.1"); + } + + @Test + public void easyRemvIP4Dom() throws Exception { + + VirtualClusterDomain domain = new VirtualClusterDomain(); + domain.setName("nacos.test.1"); + + domainsManager.chooseDomMap().put("nacos.test.1", domain); + + IpAddress ipAddress = new IpAddress(); + ipAddress.setIp("1.1.1.1"); + List ipList = new ArrayList<>(); + ipList.add(ipAddress); + domainsManager.addLock("nacos.test.1"); + domainsManager.easyRemvIP4Dom("nacos.test.1", ipList); + } + + @Test + public void searchDom() throws Exception { + VirtualClusterDomain domain = new VirtualClusterDomain(); + domain.setName("nacos.test.1"); + + domainsManager.chooseDomMap().put("nacos.test.1", domain); + + List list = domainsManager.searchDomains("nacos.test.*"); + Assert.assertNotNull(list); + Assert.assertEquals(1, list.size()); + Assert.assertEquals("nacos.test.1", list.get(0).getName()); + } +} diff --git a/naming/src/test/java/com/alibaba/nacos/naming/core/IpAddressTest.java b/naming/src/test/java/com/alibaba/nacos/naming/core/IpAddressTest.java new file mode 100644 index 00000000000..63f76f38c4c --- /dev/null +++ b/naming/src/test/java/com/alibaba/nacos/naming/core/IpAddressTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.core; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +/** + * @author dungu.zpf + */ +public class IpAddressTest { + + private IpAddress ipAddress; + + @Before + public void before() { + ipAddress = new IpAddress(); + } + + @Test + public void updateIp() { + ipAddress.setIp("1.1.1.1"); + ipAddress.setPort(1234); + ipAddress.setWeight(5); + + Assert.assertEquals("1.1.1.1", ipAddress.getIp()); + Assert.assertEquals(1234, ipAddress.getPort()); + Assert.assertEquals(5, ipAddress.getWeight(), 0.001); + } + + @Test + public void fromJson() { + ipAddress = IpAddress.fromJSON("2.2.2.2:8888_2_TEST1"); + Assert.assertEquals("2.2.2.2", ipAddress.getIp()); + Assert.assertEquals(8888, ipAddress.getPort()); + Assert.assertEquals(2, ipAddress.getWeight(), 0.001); + Assert.assertEquals("TEST1", ipAddress.getClusterName()); + } +} diff --git a/naming/src/test/java/com/alibaba/nacos/naming/misc/SwitchTest.java b/naming/src/test/java/com/alibaba/nacos/naming/misc/SwitchTest.java new file mode 100644 index 00000000000..53f564d3133 --- /dev/null +++ b/naming/src/test/java/com/alibaba/nacos/naming/misc/SwitchTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.misc; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +/** + * @author dungu.zpf + */ +public class SwitchTest { + + @Before + public void before() { + + SwitchDomain domain = new SwitchDomain(); + Switch.setDom(domain); + } + + @Test + public void udpateSwitch() { + + Switch.setCheckTimes(5); + Assert.assertEquals(5, Switch.getCheckTimes()); + + Switch.setAdWeight("1.1.1.1", 20); + Assert.assertEquals(20, Switch.getAdWeight("1.1.1.1").intValue()); + + Switch.setCacheMillis("nacos.domain.1", 5000); + Assert.assertEquals(5000, Switch.getCacheMillis("nacos.domain.1")); + + Switch.setAllDomNameCache(false); + Assert.assertTrue(!Switch.isAllDomNameCache()); + + Switch.setClientBeatInterval(1000L); + Assert.assertEquals(1000L, Switch.getClientBeatInterval()); + + Switch.setDisableAddIP(true); + Assert.assertTrue(Switch.getDisableAddIP()); + + Switch.setDistroEnabled(true); + Assert.assertTrue(Switch.isDistroEnabled()); + + } +} diff --git a/naming/src/test/java/com/alibaba/nacos/naming/raft/RaftStoreTest.java b/naming/src/test/java/com/alibaba/nacos/naming/raft/RaftStoreTest.java new file mode 100644 index 00000000000..2ca84b35d29 --- /dev/null +++ b/naming/src/test/java/com/alibaba/nacos/naming/raft/RaftStoreTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.naming.raft; + +import org.junit.Assert; +import org.junit.Test; + +/** + * @author dungu.zpf + */ +public class RaftStoreTest { + + @Test + public void wrietDatum() throws Exception { + + Datum datum = new Datum(); + datum.key = "1.2.3.4"; + datum.value = "value1"; + + RaftStore.write(datum); + + RaftStore.load("1.2.3.4"); + + Datum result = RaftCore.getDatum("1.2.3.4"); + + Assert.assertNotNull(result); + Assert.assertEquals("value1", result.value); + } +} diff --git a/naming/src/test/java/com/alibaba/nacos/naming/web/APICommandsTest.java b/naming/src/test/java/com/alibaba/nacos/naming/web/APICommandsTest.java new file mode 100644 index 00000000000..63ded0a22fc --- /dev/null +++ b/naming/src/test/java/com/alibaba/nacos/naming/web/APICommandsTest.java @@ -0,0 +1,130 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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. + */ +/** + * Alipay.com Inc. + * Copyright (c) 2004-2018 All Rights Reserved. + */ + +package com.alibaba.nacos.naming.web; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.nacos.naming.core.Cluster; +import com.alibaba.nacos.naming.core.IpAddress; +import com.alibaba.nacos.naming.core.VirtualClusterDomain; +import com.alibaba.nacos.naming.misc.UtilsAndCommons; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.springframework.mock.web.MockServletContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.nacos.naming.core.DomainsManager; + +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author en.xuze@alipay.com + * @version $Id: APICommandsTest.java, v 0.1 2018年5月14日 下午4:31:13 en.xuze@alipay.com Exp $ + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = MockServletContext.class) +@WebAppConfiguration +public class APICommandsTest { + + @InjectMocks + private ApiCommands apiCommands; + @Mock + private DomainsManager domainsManager; + private MockMvc mockmvc; + + @Before + public void before() { + MockitoAnnotations.initMocks(this); + mockmvc = MockMvcBuilders.standaloneSetup(apiCommands).build(); + } + + @Test + public void testDomCount() throws Exception { + int mockValue = 5; + JSONObject expectedResult = new JSONObject(); + expectedResult.put("count", mockValue); + Mockito.when(domainsManager.getDomCount()).thenReturn(mockValue); + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/naming/api/domCount"); + String actualValue = mockmvc.perform(builder).andReturn().getResponse().getContentAsString(); + Assert.assertTrue("UnitTest:APICommands.domCount failure!", expectedResult.toString().equals(actualValue)); + } + + @Test + public void dom() throws Exception { + VirtualClusterDomain domain = new VirtualClusterDomain(); + domain.setName("nacos.domain.1"); + Mockito.when(domainsManager.getDomain("nacos.domain.1")).thenReturn(domain); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/naming/api/dom") + .param("dom", "nacos.domain.1"); + String actualValue = mockmvc.perform(builder).andReturn().getResponse().getContentAsString(); + Assert.assertNotNull(actualValue); + JSONObject json = JSON.parseObject(actualValue); + Assert.assertNotNull(json); + Assert.assertEquals("nacos.domain.1",json.getString("name")); + } + + @Test + public void ip4Dom() throws Exception { + + VirtualClusterDomain domain = new VirtualClusterDomain(); + domain.setName("nacos.domain.1"); + Cluster cluster = new Cluster(); + cluster.setName(UtilsAndCommons.DEFAULT_CLUSTER_NAME); + cluster.setDom(domain); + domain.addCluster(cluster); + IpAddress ipAddress = new IpAddress(); + ipAddress.setIp("1.1.1.1"); + ipAddress.setPort(1234); + List list = new ArrayList<>(); + list.add(ipAddress); + + domain.onChange("iplist", JSON.toJSONString(list)); + + Mockito.when(domainsManager.getDomain("nacos.domain.1")).thenReturn(domain); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/naming/api/ip4Dom") + .param("dom", "nacos.domain.1"); + String actualValue = mockmvc.perform(builder).andReturn().getResponse().getContentAsString(); + Assert.assertNotNull(actualValue); + JSONObject json = JSON.parseObject(actualValue); + Assert.assertNotNull(json); + JSONArray ips = json.getJSONArray("ips"); + Assert.assertNotNull(ips); + Assert.assertEquals(1, ips.size()); + Assert.assertEquals("1.1.1.1", ips.getJSONObject(0).getString("ip")); + Assert.assertEquals(1234, ips.getJSONObject(0).getIntValue("port")); + } +} diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000000..e5dcffce826 --- /dev/null +++ b/pom.xml @@ -0,0 +1,685 @@ + + + + org.springframework.boot + spring-boot-starter-parent + 2.0.1.RELEASE + + + 4.0.0 + 2018 + com.alibaba.nacos + nacos-all + 0.1.0 + pom + + Alibaba NACOS ${project.version} + http://nacos.io + + 3.2.5 + + + + + git@github.com:alibaba/nacos.git + scm:git@github.com:alibaba/nacos.git + scm:git@github.com:alibaba/nacos.git + nacos-all-0.1.0 + + + + + Development List + dev-nacos+subscribe@googlegroups.com + dev-nacos+unsubscribe@googlegroups.com + dev-nacos@googlegroups.com + + + User List + users-nacos+subscribe@googlegroups.com + users-nacos+unsubscribe@googlegroups.com + users-nacos@googlegroups.com + + + Commits List + commits-nacos+subscribe@googlegroups.com + commits-nacos+unsubscribe@googlegroups.com + commits-nacos@googlegroups.com + + + + + + Alibaba Nacos + Nacos + http://nacos.io + nacos_dev@linux.alibaba.com + + + + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + repo + + + + + Alibaba Group + https://github.com/alibaba + + + + github + https://github.com/alibaba/nacos/issues + + + + UTF-8 + UTF-8 + + + false + true + + 1.8 + 1.8 + jacoco + + ${project.basedir}/../test/target/jacoco-it.exec + file:**/generated-sources/**,**/test/** + + + + + + + + + org.codehaus.mojo + versions-maven-plugin + 2.2 + + + com.github.vongosling + dependency-mediator-maven-plugin + 1.0.2 + + + org.codehaus.mojo + clirr-maven-plugin + 2.7 + + + maven-enforcer-plugin + 1.4.1 + + + enforce-ban-circular-dependencies + + enforce + + + + + + + + true + + + + org.codehaus.mojo + extra-enforcer-rules + 1.0-beta-4 + + + + + maven-compiler-plugin + 3.5.1 + + ${maven.compiler.source} + ${maven.compiler.target} + ${maven.compiler.source} + true + true + + + + maven-javadoc-plugin + 2.10.4 + + UTF-8 + + + + attach-javadocs + + jar + + + + + + maven-source-plugin + 3.0.1 + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.8 + + + rulesets/java/ali-comment.xml + rulesets/java/ali-concurrent.xml + rulesets/java/ali-constant.xml + rulesets/java/ali-exception.xml + rulesets/java/ali-flowcontrol.xml + rulesets/java/ali-naming.xml + rulesets/java/ali-oop.xml + rulesets/java/ali-orm.xml + rulesets/java/ali-other.xml + rulesets/java/ali-set.xml + + true + + + + + check + + + + + + com.alibaba.p3c + p3c-pmd + 1.3.0 + + + + + org.apache.rat + apache-rat-plugin + 0.12 + + + .travis.yml + CONTRIBUTING.md + bin/README.md + .github/* + src/test/resources/certs/* + + + + + maven-resources-plugin + 3.0.2 + + + ${project.build.sourceEncoding} + + + + org.eluder.coveralls + coveralls-maven-plugin + 4.3.0 + + + org.jacoco + jacoco-maven-plugin + 0.7.8 + + + default-prepare-agent + + prepare-agent + + + ${project.build.directory}/jacoco.exec + + + + default-prepare-agent-integration + pre-integration-test + + prepare-agent-integration + + + ${project.build.directory}/jacoco-it.exec + failsafeArgLine + + + + default-report + + report + + + + default-report-integration + + report-integration + + + + + + maven-surefire-plugin + 2.19.1 + + 1 + true + + + + org.codehaus.mojo + findbugs-maven-plugin + 3.0.4 + + + org.sonarsource.scanner.maven + sonar-maven-plugin + 3.0.2 + + + + + + + + jdk8 + + [1.8,) + + + + + + maven-javadoc-plugin + 2.10.4 + + -Xdoclint:none + + + + + + + + maven-javadoc-plugin + 2.10.4 + + -Xdoclint:none + + + + + + + release-sign-artifacts + + + performRelease + true + + + + + + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + + + + + + it-test + + + + maven-failsafe-plugin + 2.19.1 + + @{failsafeArgLine} + -Dnacos.standalone=true + + **/*ITCase.java + + + **/RestAPI_ITCase.java + + + + + + integration-test + verify + + + + + + + + + sonar-apache + + + https://builds.apache.org/analysis + + + + + + + org.codehaus.mojo + findbugs-maven-plugin + 3.0.1 + + + + + + + config + core + naming + test + client + example + common + distribution + console + + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + + + ${project.groupId} + nacos-config + ${project.version} + + + ${project.groupId} + nacos-core + ${project.version} + + + ${project.groupId} + nacos-naming + ${project.version} + + + ${project.groupId} + nacos-client + ${project.version} + + + ${project.groupId} + nacos-test + ${project.version} + + + ${project.groupId} + nacos-common + ${project.version} + + + ${project.groupId} + nacos-console + ${project.version} + + + ${project.groupId} + nacos-distribution + ${project.version} + + + ${project.groupId} + nacos-example + ${project.version} + + + org.slf4j + slf4j-api + 1.7.7 + + + ch.qos.logback + logback-classic + 1.2.3 + + + ch.qos.logback + logback-core + 1.2.3 + + + commons-cli + commons-cli + 1.2 + + + io.netty + netty-all + 4.0.42.Final + + + com.alibaba + fastjson + 1.2.47 + + + com.ning + async-http-client + 1.7.17 + + + org.apache.commons + commons-lang3 + 3.4 + + + commons-lang + commons-lang + 2.6 + + + commons-collections + commons-collections + 3.2.2 + + + commons-logging + commons-logging + 1.2 + + + org.codehaus.jackson + jackson-core-asl + 1.9.10 + + + com.taobao.middleware + logger.api + 0.2.0 + + + apache-log4j + log4j + 1.2.15 + + + log4j + log4j + 1.2.17 + + + org.apache.logging.log4j + log4j-core + 2.10.0 + + + org.apache.logging.log4j + log4j-slf4j-impl + 2.10.0 + + + com.github.spotbugs + spotbugs-annotations + 3.1.3 + + + + + javax.ws.rs + javax.ws.rs + 2.1 + + + javax.servlet + servlet-api + 3.0 + provided + + + taglibs + standard + 1.1.2 + + + + commons-io + commons-io + 2.2 + + + mysql + mysql-connector-java + 5.1.34 + + + + commons-dbcp + commons-dbcp + 1.4 + + + + org.apache.derby + derby + 10.10.1.1 + + + + cglib + cglib-nodep + 2.1 + + + org.apache.httpcomponents + httpasyncclient + 4.1.3 + + + net.jcip + jcip-annotations + 1.0 + + + org.codehaus.jackson + jackson-mapper-lgpl + 1.9.6 + + + + + + + org.apache.mina + mina-core + 2.0.0-RC1 + + + com.google.guava + guava + 19.0 + + + org.javatuples + javatuples + 1.2 + + + org.apache.velocity + velocity + 1.7 + + + org.apache.velocity + velocity-tools + 2.0 + + + commons-logging + commons-logging + + + commons-digester + commons-digester + + + + + org.apache.httpcomponents + httpcore + 4.4.1 + + + org.apache.httpcomponents + httpclient + 4.5 + + + commons-logging + commons-logging + + + + + + + diff --git a/style/codeStyle.md b/style/codeStyle.md new file mode 100644 index 00000000000..83d2236ddf3 --- /dev/null +++ b/style/codeStyle.md @@ -0,0 +1,31 @@ +# Nacos + +## Nacos Code Style +Nacos code style Comply with Alibaba Java Coding Guidelines. + +Nacos的编码规范遵从于《阿里巴巴JAVA开发规约》。 + + +## Guidelines +[Alibaba-Java-Coding-Guidelines](https://alibaba.github.io/Alibaba-Java-Coding-Guidelines/) + +[阿里巴巴JAVA开发规约](https://github.com/alibaba/p3c/blob/master/%E9%98%BF%E9%87%8C%E5%B7%B4%E5%B7%B4Java%E5%BC%80%E5%8F%91%E6%89%8B%E5%86%8C%EF%BC%88%E8%AF%A6%E5%B0%BD%E7%89%88%EF%BC%89.pdf) + + +## IDE Plugin Install(not necessary) + +*It is not necessary to install, if you want to find a problem when you are coding.* + +*不是必须安装,如果你需要在开发的时候实时发现问题的话,你需要安装。* + +### idea IDE +[p3c-idea-plugin-install](https://github.com/alibaba/p3c/blob/master/idea-plugin/README.md) + +[p3c插件idea IDE上安装方法](https://github.com/alibaba/p3c/blob/master/idea-plugin/README_cn.md) + +### eclipse IDE +[p3c-eclipse-plugin-install](https://github.com/alibaba/p3c/blob/master/eclipse-plugin/README.md) + +[p3c插件eclipse IDE上安装方法](https://github.com/alibaba/p3c/blob/master/eclipse-plugin/README_cn.md) + +### Acknowledgement [Alibaba p3c](https://github.com/alibaba/p3c) \ No newline at end of file diff --git a/test/it_test.log b/test/it_test.log new file mode 100644 index 00000000000..49e95beb2c6 --- /dev/null +++ b/test/it_test.log @@ -0,0 +1,3 @@ +2018-04-25 17:56:23,361 - nacosSmoke -0 [main] INFO - nacosSmoke: setUp; +2018-04-25 17:56:23,363 - nacosSmoke -2 [main] INFO - nacosSmoke :testSmoke +2018-04-25 17:56:23,363 - nacosSmoke -2 [main] INFO - nacosSmoke: tearDown; diff --git a/test/pom.xml b/test/pom.xml new file mode 100644 index 00000000000..ed43757eede --- /dev/null +++ b/test/pom.xml @@ -0,0 +1,77 @@ + + + com.alibaba.nacos + nacos-all + 0.1.0 + + 4.0.0 + + nacos-test + jar + + nacos-test ${project.version} + http://maven.apache.org + + + UTF-8 + + + + + + log4j + log4j + + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + ch.qos.logback + logback-core + + + + com.google.truth + truth + 0.30 + + + + ${project.groupId} + nacos-client + + + ${project.groupId} + nacos-config + + + ${project.groupId} + nacos-naming + + + ${project.groupId} + nacos-core + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.9 + + -Dnacos.standalone=true + + + + + diff --git a/test/src/main/java/com/alibaba/nacos/test/App.java b/test/src/main/java/com/alibaba/nacos/test/App.java new file mode 100644 index 00000000000..bf3ea7e9c0e --- /dev/null +++ b/test/src/main/java/com/alibaba/nacos/test/App.java @@ -0,0 +1,28 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.test; + +/** + * Hello world! + * @author xxc + */ +public class App +{ + public static void main( String[] args ) + { + System.out.println( "Hello World!" ); + } +} diff --git a/test/src/test/java/com/alibaba/nacos/test/AppTest.java b/test/src/test/java/com/alibaba/nacos/test/AppTest.java new file mode 100644 index 00000000000..fa143f92d51 --- /dev/null +++ b/test/src/test/java/com/alibaba/nacos/test/AppTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.test; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Unit test for simple App. + */ +public class AppTest + extends TestCase +{ + /** + * Create the test case + * + * @param testName name of the test case + */ + public AppTest( String testName ) + { + super( testName ); + } + + /** + * @return the suite of tests being tested + */ + public static Test suite() + { + return new TestSuite( AppTest.class ); + } + + /** + * Rigourous Test :-) + */ + public void testApp() + { + assertTrue( true ); + } +} diff --git a/test/src/test/java/com/alibaba/nacos/test/config/ConfigAPI_ITCase.java b/test/src/test/java/com/alibaba/nacos/test/config/ConfigAPI_ITCase.java new file mode 100644 index 00000000000..01ac9e830bd --- /dev/null +++ b/test/src/test/java/com/alibaba/nacos/test/config/ConfigAPI_ITCase.java @@ -0,0 +1,564 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.test.config; + +import com.alibaba.nacos.api.NacosFactory; +import com.alibaba.nacos.api.PropertyKeyConst; +import com.alibaba.nacos.api.config.ConfigService; +import com.alibaba.nacos.api.config.listener.Listener; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.config.server.Config; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.test.context.junit4.SpringRunner; +import java.util.Properties; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicInteger; + + +/** + * @author xiaochun.xxc + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Config.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class ConfigAPI_ITCase { + public static final long TIME_OUT = 3000; + public ConfigService iconfig = null; + String SPECIAL_CHARACTERS = "!@#$%^&*()_+-=_|/'?."; + String dataId = "yanlin"; + String group = "yanlin"; + + @LocalServerPort + private int port; + + @Autowired + private TestRestTemplate restTemplate; + + @Before + public void setUp() throws Exception { + Properties properties = new Properties(); + properties.put(PropertyKeyConst.SERVER_ADDR, "127.0.0.1"+":"+port); + iconfig = NacosFactory.createConfigService(properties); + } + + @After + public void cleanup() throws Exception { + } + + /** + * @TCDescription : nacos_正常获取数据 + * @TestStep : TODO Test steps + * @ExpectResult : TODO expect results + */ + @Test(timeout = 3*TIME_OUT) + public void nacos_getconfig_1() throws Exception { + final String content = "test"; + boolean result = iconfig.publishConfig(dataId, group, content); + Thread.sleep(TIME_OUT); + Assert.assertTrue(result); + String value = iconfig.getConfig(dataId, group, TIME_OUT); + Assert.assertEquals(content, value); + result = iconfig.removeConfig(dataId, group); + Thread.sleep(TIME_OUT); + Assert.assertTrue(result); + value = iconfig.getConfig(dataId, group, TIME_OUT); + System.out.println(value); + Assert.assertEquals(null, value); + } + + /** + * @TCDescription : nacos_服务端无配置时,获取配置 + * @throws Exception + */ + @Test(timeout = 5*TIME_OUT) + public void nacos_getconfig_2() throws Exception { + String content = iconfig.getConfig(dataId, "nacos", TIME_OUT); + Assert.assertNull(content); + } + + /** + * @TCDescription : nacos_获取配置时dataId为null + * @throws Exception + */ + @Test(timeout = 5*TIME_OUT) + public void nacos_getconfig_3() throws Exception { + try { + String content = iconfig.getConfig(null, group, TIME_OUT); + } catch (Exception e) { + Assert.assertTrue(true); + return; + } + Assert.assertTrue(false); + } + + /** + * @TCDescription : nacos_获取配置时group为null + * @throws Exception + */ + @Test(timeout = 5*TIME_OUT) + public void nacos_getconfig_4() throws Exception { + final String content = "test"; + + boolean result = iconfig.publishConfig(dataId, null, content); + Thread.sleep(2*TIME_OUT); + Assert.assertTrue(result); + + String value = iconfig.getConfig(dataId, null, TIME_OUT); + Assert.assertEquals(content, value); + + result = iconfig.removeConfig(dataId, null); + Thread.sleep(TIME_OUT); + Assert.assertTrue(result); + } + + /** + * @TCDescription : nacos_服务端无该配置项时,正常创建配置 + * @throws Exception + */ + @Test(timeout = 5*TIME_OUT) + public void nacos_publishConfig_1() throws Exception { + final String content = "publishConfigTest"; + boolean result = iconfig.publishConfig(dataId, group, content); + Thread.sleep(TIME_OUT); + Assert.assertTrue(result); + result = iconfig.removeConfig(dataId, group); + Assert.assertTrue(result); + } + + /** + * @TCDescription : nacos_服务端有该配置项时,正常修改配置 + * @throws Exception + */ + @Test(timeout = 5*TIME_OUT) + public void nacos_publishConfig_2() throws Exception { + final String content = "publishConfigTest"; + boolean result = iconfig.publishConfig(dataId, group, content); + Thread.sleep(TIME_OUT); + Assert.assertTrue(result); + + final String content1 = "test.abc"; + result = iconfig.publishConfig(dataId, group, content1); + Thread.sleep(TIME_OUT); + String value = iconfig.getConfig(dataId, group, TIME_OUT); + Assert.assertEquals(content1, value); + } + + /** + * @TCDescription : nacos_发布配置时包含特殊字符 + * @throws Exception + */ + @Test(timeout = 5*TIME_OUT) + public void nacos_publishConfig_3() throws Exception { + String content = "test" + SPECIAL_CHARACTERS; + boolean result = iconfig.publishConfig(dataId, group, content); + Thread.sleep(TIME_OUT); + Assert.assertTrue(result); + + String value = iconfig.getConfig(dataId, group, TIME_OUT); + Assert.assertEquals(content, value); + } + + /** + * @TCDescription : nacos_发布配置时dataId为null + * @throws Exception + */ + @Test(timeout = 5*TIME_OUT) + public void nacos_publishConfig_4() throws Exception { + try { + String content = "test"; + boolean result = iconfig.publishConfig(null, group, content); + Thread.sleep(TIME_OUT); + Assert.assertTrue(result); + } catch (Exception e) { + Assert.assertTrue(true); + return; + } + Assert.assertTrue(false); + } + + /** + * @TCDescription : nacos_发布配置时group为null + * @throws Exception + */ + @Test(timeout = 5*TIME_OUT) + public void nacos_publishConfig_5() throws Exception { + String content = "test"; + boolean result = iconfig.publishConfig(dataId, null, content); + Thread.sleep(2*TIME_OUT); + Assert.assertTrue(result); + + String value = iconfig.getConfig(dataId, null, TIME_OUT); + Assert.assertEquals(content, value); + } + + + /** + * @TCDescription : nacos_发布配置时配置内容为null + * @throws Exception + */ + @Test(timeout = 5*TIME_OUT) + public void nacos_publishConfig_6() throws Exception { + String content = null; + try { + boolean result = iconfig.publishConfig(dataId, group, content); + Thread.sleep(TIME_OUT); + } catch (Exception e) { + Assert.assertTrue(true); + return; + } + Assert.assertTrue(false); + } + + /** + * @TCDescription : nacos_发布配置时配置内容包含中文字符 + * @throws Exception + */ + @Test(timeout = 5*TIME_OUT) + public void nacos_publishConfig_7() throws Exception { + String content = "阿里abc"; + boolean result = iconfig.publishConfig(dataId, group, content); + Thread.sleep(TIME_OUT); + Assert.assertTrue(result); + String value = iconfig.getConfig(dataId, group, TIME_OUT); + Assert.assertEquals(content, value); + } + + /** + * @TCDescription : nacos_服务端有该配置项时,正常删除配置 + * @throws Exception + */ + @Test(timeout = 5*TIME_OUT) + public void nacos_removeConfig_1() throws Exception { + String content = "test"; + boolean result = iconfig.publishConfig(dataId, group, content); + Thread.sleep(TIME_OUT); + Assert.assertTrue(result); + + result = iconfig.removeConfig(dataId, group); + Thread.sleep(TIME_OUT); + Assert.assertTrue(result); + String value = iconfig.getConfig(dataId, group, TIME_OUT); + Assert.assertEquals(null, value); + } + + /** + * @TCDescription : nacos_服务端无该配置项时,配置删除失败 + * @throws Exception + */ + @Test(timeout = 5*TIME_OUT) + public void nacos_removeConfig_2() throws Exception { + group += "removeConfig2"; + boolean result = iconfig.removeConfig(dataId, group); + Assert.assertTrue(result); + } + + /** + * @TCDescription : nacos_删除配置时dataId为null + * @throws Exception + */ + @Test(timeout = 5*TIME_OUT) + public void nacos_removeConfig_3() throws Exception { + try { + boolean result = iconfig.removeConfig(null, group); + Assert.assertTrue(result); + } catch (Exception e) { + Assert.assertTrue(true); + return; + } + Assert.assertTrue(false); + } + + /** + * @TCDescription : nacos_删除配置时group为null + * @throws Exception + */ + @Test(timeout = 5*TIME_OUT) + public void nacos_removeConfig_4() throws Exception { + boolean result = iconfig.removeConfig(dataId, null); + Assert.assertTrue(result); + } + + /** + * @TCDescription : nacos_添加对dataId的监听,在服务端修改配置后,获取监听后的修改的配置 + * @throws Exception + */ + @Test(timeout = 5*TIME_OUT) + public void nacos_addListener_1() throws Exception { + final AtomicInteger count = new AtomicInteger(0); + final String content = "test-abc"; + boolean result = iconfig.publishConfig(dataId, group, content); + Assert.assertTrue(result); + + Listener ml = new Listener() { + @Override + public void receiveConfigInfo(String configInfo) { + // TODO Auto-generated method stub + System.out.println("recieve2:" + configInfo); + count.incrementAndGet(); + Assert.assertEquals(content, configInfo); + } + + @Override + public Executor getExecutor() { + // TODO Auto-generated method stub + return null; + } + }; + iconfig.addListener(dataId, group, ml); + while (count.get() == 0) { + Thread.sleep(2000); + } + Assert.assertEquals(1, count.get()); + iconfig.removeListener(dataId, group, ml); + } + + /** + * @TCDescription : nacos_设置监听器为null,抛出异常信息 + * @TestStep : TODO Test steps + * @ExpectResult : TODO expect results + * @author xiaochun.xxc + * @since 3.6.8 + */ + @Test(timeout = TIME_OUT) + public void nacos_addListener_2() { + try { + iconfig.addListener(dataId, group, null); + Assert.assertFalse(true); + } catch (Exception e) { + Assert.assertFalse(false); + } + } + + + /** + * @TCDescription : nacos_添加对dataId的监听,修改服务端配置,正常推送并只推送一次 + * @TestStep : TODO Test steps + * @ExpectResult : TODO expect results + * @author xiaochun.xxc + * @since 3.6.8 + */ + @Test(timeout = 5*TIME_OUT) + public void nacos_addListener_3() throws InterruptedException, NacosException { + final AtomicInteger count = new AtomicInteger(0); + final String content = "test-abc"; + boolean result = iconfig.publishConfig(dataId, group, content); + Assert.assertTrue(result); + + Listener ml = new Listener() { + @Override + public void receiveConfigInfo(String configInfo) { + // TODO Auto-generated method stub + count.incrementAndGet(); + Assert.assertEquals(content, configInfo); + } + + @Override + public Executor getExecutor() { + // TODO Auto-generated method stub + return null; + } + }; + iconfig.addListener(dataId, group, ml); + while (count.get() == 0) { + Thread.sleep(2000); + } + Assert.assertEquals(1, count.get()); + iconfig.removeListener(dataId, group, ml); + } + + /** + * @TCDescription : nacos_服务端无配置时,添加对dataId的监听 + * @TestStep : TODO Test steps + * @ExpectResult : TODO expect results + * @author xiaochun.xxc + * @since 3.6.8 + */ + @Test(timeout = 5*TIME_OUT) + public void nacos_addListener_4() throws Exception { + final AtomicInteger count = new AtomicInteger(0); + + iconfig.removeConfig(dataId, group); + Thread.sleep(TIME_OUT); + + Listener ml = new Listener() { + @Override + public void receiveConfigInfo(String configInfo) { + // TODO Auto-generated method stub + count.incrementAndGet(); + } + + @Override + public Executor getExecutor() { + // TODO Auto-generated method stub + return null; + } + }; + iconfig.addListener(dataId, group, ml); + Thread.sleep(TIME_OUT); + String content = "test-abc"; + boolean result = iconfig.publishConfig(dataId, group, content); + Assert.assertTrue(result); + + while (count.get() == 0) { + Thread.sleep(3000); + } + Assert.assertEquals(1, count.get()); + iconfig.removeListener(dataId, group, ml); + } + + /** + * @TCDescription : nacos_正常移除监听器 + * @TestStep : TODO Test steps + * @ExpectResult : TODO expect results + * @author xiaochun.xxc + * @since 3.6.8 + */ + @Test(timeout = 5*TIME_OUT) + public void nacos_removeListener_1() throws Exception { + iconfig.addListener(dataId, group, new Listener() { + @Override + public void receiveConfigInfo(String configInfo) { + // TODO Auto-generated method stub + Assert.assertTrue(false); + } + + @Override + public Executor getExecutor() { + // TODO Auto-generated method stub + return null; + } + }); + Thread.sleep(TIME_OUT); + try { + iconfig.removeListener(dataId, group, new Listener() { + @Override + public void receiveConfigInfo(String configInfo) { + // TODO Auto-generated method stub + System.out.println("remove recieve:" + configInfo); + } + + @Override + public Executor getExecutor() { + // TODO Auto-generated method stub + return null; + } + }); + } catch (Exception e) { + + } + } + + /** + * @TCDescription : nacos_移除无该项dataId的监听器 + * @TestStep : TODO Test steps + * @ExpectResult : TODO expect results + * @author xiaochun.xxc + * @since 3.6.8 + */ + @Test(timeout = TIME_OUT) + public void nacos_removeListener_2() { + group += "test.nacos"; + try { + iconfig.removeListener(dataId, group, new Listener() { + @Override + public void receiveConfigInfo(String configInfo) { + // TODO Auto-generated method stub + } + + @Override + public Executor getExecutor() { + // TODO Auto-generated method stub + return null; + } + }); + } catch (Exception e) { + Assert.assertTrue(false); + } + } + + /** + * @TCDescription : nacos_存在多个监听器时,删除最后一个监听器 + * @TestStep : TODO Test steps + * @ExpectResult : TODO expect results + * @author xiaochun.xxc + * @since 3.6.8 + */ + @Test(timeout = 5*TIME_OUT) + public void nacos_removeListener_3() throws Exception { + final String contentRemove = "test-abc-two"; + final AtomicInteger count = new AtomicInteger(0); + + Listener ml = new Listener() { + @Override + public Executor getExecutor() { + return null; + } + + @Override + public void receiveConfigInfo(String configInfo) { + count.incrementAndGet(); + } + }; + Listener ml1 = new Listener() { + @Override + public Executor getExecutor() { + return null; + } + + @Override + public void receiveConfigInfo(String configInfo) { + //System.out.println("ml1 remove listener recieve:" + configInfo); + count.incrementAndGet(); + Assert.assertEquals(contentRemove, configInfo); + } + }; + iconfig.addListener(dataId, group, ml); + iconfig.addListener(dataId, group, ml1); + + iconfig.removeListener(dataId, group, ml); + Thread.sleep(TIME_OUT); + + boolean result = iconfig.publishConfig(dataId, group, contentRemove); + Thread.sleep(TIME_OUT); + Assert.assertTrue(result); + + while (count.get() == 0) { + Thread.sleep(3000); + } + Assert.assertNotEquals(0, count.get()); + } + + /** + * @TCDescription : nacos_监听器为null时 + * @TestStep : TODO Test steps + * @ExpectResult : TODO expect results + * @author xiaochun.xxc + * @since 3.6.8 + */ + @Test(timeout = TIME_OUT) + public void nacos_removeListener_4() { + iconfig.removeListener(dataId, group, (Listener) null); + Assert.assertTrue(true); + } + +} diff --git a/test/src/test/java/com/alibaba/nacos/test/naming/DeregisterInstance_ITCase.java b/test/src/test/java/com/alibaba/nacos/test/naming/DeregisterInstance_ITCase.java new file mode 100644 index 00000000000..9b80f515b1f --- /dev/null +++ b/test/src/test/java/com/alibaba/nacos/test/naming/DeregisterInstance_ITCase.java @@ -0,0 +1,115 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.test.naming; + +import com.alibaba.nacos.api.naming.NamingFactory; +import com.alibaba.nacos.api.naming.NamingService; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.naming.NamingApp; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static com.alibaba.nacos.test.naming.NamingBase.TEST_PORT; +import static com.alibaba.nacos.test.naming.NamingBase.randomDomainName; + +/** + * Created by wangtong.wt on 2018/6/20. + * + * @author wangtong.wt + * @date 2018/6/20 + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = NamingApp.class, properties = {"server.servlet.context-path=/nacos"}, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class DeregisterInstance_ITCase { + + private NamingService naming; + @LocalServerPort + private int port; + @Before + public void init() throws Exception{ + if (naming == null) { + TimeUnit.SECONDS.sleep(10); + naming = NamingFactory.createNamingService("127.0.0.1"+":"+port); + } + } + + /** + * 删除service中默认cluster的一个ip + * @throws Exception + */ + @Test + public void dregDomTest() throws Exception { + String serviceName = randomDomainName(); + naming.registerInstance(serviceName, "127.0.0.1", TEST_PORT); + naming.registerInstance(serviceName, "127.0.0.2", TEST_PORT); + + TimeUnit.SECONDS.sleep(3); + + List instances = naming.getAllInstances(serviceName); + + Assert.assertEquals(instances.size(), 2); + + naming.deregisterInstance(serviceName, "127.0.0.1", TEST_PORT); + + TimeUnit.SECONDS.sleep(2); + + instances = naming.getAllInstances(serviceName); + + Assert.assertEquals(instances.size(), 1); + Assert.assertEquals(instances.get(0).getIp(), "127.0.0.2"); + + } + + /** + * 删除service中指定cluster的一个ip + * @throws Exception + */ + @Test(expected = IllegalStateException.class) + public void dregDomClusterTest() throws Exception { + String serviceName = randomDomainName(); + naming.registerInstance(serviceName, "127.0.0.1", TEST_PORT, "c1"); + naming.registerInstance(serviceName, "127.0.0.2", TEST_PORT, "c2"); + + TimeUnit.SECONDS.sleep(3); + + List instances = naming.getAllInstances(serviceName); + + Assert.assertEquals(instances.size(), 2); + + naming.deregisterInstance(serviceName, "127.0.0.1", TEST_PORT, "c1"); + + TimeUnit.SECONDS.sleep(2); + + instances = naming.getAllInstances(serviceName); + + Assert.assertEquals(instances.size(), 1); + + instances = naming.getAllInstances(serviceName, Arrays.asList("c2")); + Assert.assertEquals(instances.size(), 1); + + instances = naming.getAllInstances(serviceName, Arrays.asList("c1")); + } +} diff --git a/test/src/test/java/com/alibaba/nacos/test/naming/NamingBase.java b/test/src/test/java/com/alibaba/nacos/test/naming/NamingBase.java new file mode 100644 index 00000000000..21b9865b1ee --- /dev/null +++ b/test/src/test/java/com/alibaba/nacos/test/naming/NamingBase.java @@ -0,0 +1,174 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.test.naming; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.alibaba.nacos.api.naming.pojo.AbstractHealthChecker; +import com.alibaba.nacos.api.naming.pojo.Cluster; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.api.naming.pojo.Service; + +/** + * @author dungu.zpf + */ +public class NamingBase { + + + public static final String TEST_DOM_1 = "nacos.test.1"; + public static final String TEST_IP_4_DOM_1 = "127.0.0.1"; + public static final String TEST_PORT_4_DOM_1 = "8080"; + public static final String TEST_PORT2_4_DOM_1 = "8888"; + public static final String TEST_TOKEN_4_DOM_1 = "abc"; + public static final String TEST_NEW_CLUSTER_4_DOM_1 = "TEST1"; + + public static final String TEST_DOM_2 = "nacos.test.2"; + public static final String TEST_IP_4_DOM_2 = "127.0.0.2"; + public static final String TEST_PORT_4_DOM_2 = "7070"; + public static final String TETS_TOKEN_4_DOM_2 = "xyz"; + + public static final int TEST_PORT = 8080; + + public static String randomDomainName() { + StringBuilder sb = new StringBuilder(); + sb.append("jinhan"); + for (int i = 0; i < 2; i++) { + sb.append(RandomUtils.getStringWithNumAndCha(5)); + sb.append("."); + } + int i = RandomUtils.getIntegerBetween(0, 2); + if (i == 0) { + sb.append("com"); + } else { + sb.append("net"); + } + return sb.toString(); + } + + public static Instance getInstance(String serviceName) { + Instance instance = new Instance(); + instance.setIp("127.0.0.1"); + instance.setPort(TEST_PORT); + instance.setHealthy(true); + instance.setWeight(2.0); + Map instanceMeta = new HashMap<>(); + instanceMeta.put("site", "et2"); + instance.setMetadata(instanceMeta); + + Service service = new Service(serviceName); + service.setApp("nacos-naming"); + service.setHealthCheckMode("server"); + service.setProtectThreshold(0.8F); + service.setGroup("CNCF"); + Map serviceMeta = new HashMap<>(); + serviceMeta.put("symmetricCall", "true"); + service.setMetadata(serviceMeta); + instance.setService(service); + + Cluster cluster = new Cluster(); + cluster.setName("c1"); + AbstractHealthChecker.Http healthChecker = new AbstractHealthChecker.Http(); + healthChecker.setExpectedResponseCode(400); + healthChecker.setHeaders("Client-Version|Nacos"); + healthChecker.setPath("/xxx.html"); + cluster.setHealthChecker(healthChecker); + Map clusterMeta = new HashMap<>(); + clusterMeta.put("xxx", "yyyy"); + cluster.setMetadata(clusterMeta); + + instance.setCluster(cluster); + + return instance; + } + + public static boolean verifyInstance(Instance i1, Instance i2) { + + if (!i1.getIp().equals(i2.getIp()) || i1.getPort() != i2.getPort() || + i1.getWeight() != i2.getWeight() || i1.isHealthy() != i2.isHealthy() || + !i1.getMetadata().equals(i2.getMetadata())) { + return false; + } + + //Service service1 = i1.getService(); + //Service service2 = i2.getService(); + // + //if (!service1.getApp().equals(service2.getApp()) || !service1.getGroup().equals(service2.getGroup()) || + // !service1.getMetadata().equals(service2.getMetadata()) || !service1.getName().equals(service2.getName()) || + // service1.getProtectThreshold() != service2.getProtectThreshold() || + // service1.isEnableClientBeat() != service2.isEnableClientBeat() || + // service1.isEnableHealthCheck() != service2.isEnableHealthCheck()) { + // return false; + //} + + //Cluster cluster1 = i1.getCluster(); + //Cluster cluster2 = i2.getCluster(); + // + //if (!cluster1.getName().equals(cluster2.getName()) || + // cluster1.getDefaultCheckPort() != cluster2.getDefaultCheckPort() || + // cluster1.getDefaultPort() != cluster2.getDefaultPort() || + // !cluster1.getServiceName().equals(cluster2.getServiceName()) || + // !cluster1.getMetadata().equals(cluster2.getMetadata())|| + // cluster1.isUseIPPort4Check() != cluster2.isUseIPPort4Check()) { + // return false; + //} + // + //HealthChecker healthChecker1 = cluster1.getHealthChecker(); + //HealthChecker healthChecker2 = cluster2.getHealthChecker(); + // + //if (healthChecker1.getClass().getName() != healthChecker2.getClass().getName()) { + // return false; + //} + // + //if (healthChecker1 instanceof HealthChecker.Http) { + // HealthChecker.Http h1 = (HealthChecker.Http) healthChecker1; + // HealthChecker.Http h2 = (HealthChecker.Http) healthChecker2; + // + // if (h1.getExpectedResponseCode() != h2.getExpectedResponseCode() || + // !h1.getHeaders().equals(h2.getHeaders()) || + // !h1.getPath().equals(h2.getPath()) || + // !h1.getCustomHeaders().equals(h2.getCustomHeaders())) { + // return false; + // } + //} + + return true; + + } + + public static boolean verifyInstanceList(List instanceList1, List instanceList2) { + Map instanceMap = new HashMap<>(); + for (Instance instance : instanceList1) { + instanceMap.put(instance.getIp(), instance); + } + + Map instanceGetMap = new HashMap<>(); + for (Instance instance : instanceList2) { + instanceGetMap.put(instance.getIp(), instance); + } + + for (String ip : instanceMap.keySet()) { + if (!instanceGetMap.containsKey(ip)) { + return false; + } + if (!verifyInstance(instanceMap.get(ip), instanceGetMap.get(ip))) { + return false; + } + } + return true; + } +} diff --git a/test/src/test/java/com/alibaba/nacos/test/naming/Params.java b/test/src/test/java/com/alibaba/nacos/test/naming/Params.java new file mode 100644 index 00000000000..ed8370ed651 --- /dev/null +++ b/test/src/test/java/com/alibaba/nacos/test/naming/Params.java @@ -0,0 +1,42 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.test.naming; + +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +/** + * @author dungu.zpf + */ +public class Params { + + private MultiValueMap paramMap; + + public static Params newParams() { + Params params = new Params(); + params.paramMap = new LinkedMultiValueMap<>(); + return params; + } + + public Params appendParam(String name, String value) { + this.paramMap.add(name, value); + return this; + } + + public MultiValueMap done() { + return paramMap; + } +} diff --git a/test/src/test/java/com/alibaba/nacos/test/naming/RandomUtils.java b/test/src/test/java/com/alibaba/nacos/test/naming/RandomUtils.java new file mode 100644 index 00000000000..c6bd7f69dbd --- /dev/null +++ b/test/src/test/java/com/alibaba/nacos/test/naming/RandomUtils.java @@ -0,0 +1,348 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.test.naming; + +import java.util.*; + +/** + * Created by wangtong.wt on 2018/6/20. + * + * @author wangtong.wt + * @date 2018/6/20 + */ +public class RandomUtils { + private static Random rd = new Random(); + private static int UNICODE_START = 19968; + private static int UNICODE_END = 40864; + + private RandomUtils() { + } + + public static long getLong() { + return rd.nextLong(); + } + + public static long getLongMoreThanZero() { + long res; + for(res = rd.nextLong(); res <= 0L; res = rd.nextLong()) { + ; + } + + return res; + } + + public static long getLongLessThan(long n) { + long res = rd.nextLong(); + return res % n; + } + + public static long getLongMoreThanZeroLessThan(long n) { + long res; + for(res = getLongLessThan(n); res <= 0L; res = getLongLessThan(n)) { + ; + } + + return res; + } + + public static long getLongBetween(long n, long m) { + if (m <= n) { + return n; + } else { + long res = getLongMoreThanZero(); + return n + res % (m - n); + } + } + + public static int getInteger() { + return rd.nextInt(); + } + + public static int getIntegerMoreThanZero() { + int res; + for(res = rd.nextInt(); res <= 0; res = rd.nextInt()) { + ; + } + + return res; + } + + public static int getIntegerLessThan(int n) { + int res = rd.nextInt(); + return res % n; + } + + public static int getIntegerMoreThanZeroLessThan(int n) { + int res; + for(res = rd.nextInt(n); res == 0; res = rd.nextInt(n)) { + ; + } + + return res; + } + + public static int getIntegerBetween(int n, int m) { + if (m == n) { + return n; + } else { + int res = getIntegerMoreThanZero(); + return n + res % (m - n); + } + } + + private static char getChar(int[] arg) { + int size = arg.length; + int c = rd.nextInt(size / 2); + c *= 2; + return (char)getIntegerBetween(arg[c], arg[c + 1]); + } + + private static String getString(int n, int[] arg) { + StringBuilder res = new StringBuilder(); + + for(int i = 0; i < n; ++i) { + res.append(getChar(arg)); + } + + return res.toString(); + } + + public static String getStringWithCharacter(int n) { + int[] arg = new int[]{97, 123, 65, 91}; + return getString(n, arg); + } + + public static String getStringWithNumber(int n) { + int[] arg = new int[]{48, 58}; + return getString(n, arg); + } + + public static String getStringWithNumAndCha(int n) { + int[] arg = new int[]{97, 123, 65, 91, 48, 58}; + return getString(n, arg); + } + + public static String getRandomString(int length) { + StringBuffer buffer = new StringBuffer("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); + StringBuffer sb = new StringBuffer(); + Random random = new Random(); + int range = buffer.length(); + + for(int i = 0; i < length; ++i) { + sb.append(buffer.charAt(random.nextInt(range))); + } + + return sb.toString(); + } + + public static String getStringShortenThan(int n) { + int len = getIntegerMoreThanZeroLessThan(n); + return getStringWithCharacter(len); + } + + public static String getStringWithNumAndChaShortenThan(int n) { + int len = getIntegerMoreThanZeroLessThan(n); + return getStringWithNumAndCha(len); + } + + public static String getStringBetween(int n, int m) { + int len = getIntegerBetween(n, m); + return getStringWithCharacter(len); + } + + public static String getStringWithNumAndChaBetween(int n, int m) { + int len = getIntegerBetween(n, m); + return getStringWithNumAndCha(len); + } + + public static String getStringWithPrefix(int n, String prefix) { + int len = prefix.length(); + if (n <= len) { + return prefix; + } else { + len = n - len; + StringBuilder res = new StringBuilder(prefix); + res.append(getStringWithCharacter(len)); + return res.toString(); + } + } + + public static String getStringWithSuffix(int n, String suffix) { + int len = suffix.length(); + if (n <= len) { + return suffix; + } else { + len = n - len; + StringBuilder res = new StringBuilder(); + res.append(getStringWithCharacter(len)); + res.append(suffix); + return res.toString(); + } + } + + public static String getStringWithBoth(int n, String prefix, String suffix) { + int len = prefix.length() + suffix.length(); + StringBuilder res = new StringBuilder(prefix); + if (n <= len) { + return res.append(suffix).toString(); + } else { + len = n - len; + res.append(getStringWithCharacter(len)); + res.append(suffix); + return res.toString(); + } + } + + public static String getCheseWordWithPrifix(int n, String prefix) { + int len = prefix.length(); + if (n <= len) { + return prefix; + } else { + len = n - len; + StringBuilder res = new StringBuilder(prefix); + res.append(getCheseWord(len)); + return res.toString(); + } + } + + public static String getCheseWordWithSuffix(int n, String suffix) { + int len = suffix.length(); + if (n <= len) { + return suffix; + } else { + len = n - len; + StringBuilder res = new StringBuilder(); + res.append(getCheseWord(len)); + res.append(suffix); + return res.toString(); + } + } + + public static String getCheseWordWithBoth(int n, String prefix, String suffix) { + int len = prefix.length() + suffix.length(); + StringBuilder res = new StringBuilder(prefix); + if (n <= len) { + return res.append(suffix).toString(); + } else { + len = n - len; + res.append(getCheseWord(len)); + res.append(suffix); + return res.toString(); + } + } + + public static String getCheseWord(int len) { + StringBuilder res = new StringBuilder(); + + for(int i = 0; i < len; ++i) { + char str = getCheseChar(); + res.append(str); + } + + return res.toString(); + } + + private static char getCheseChar() { + return (char)(UNICODE_START + rd.nextInt(UNICODE_END - UNICODE_START)); + } + + public static boolean getBoolean() { + return getIntegerMoreThanZeroLessThan(3) == 1; + } + + public static void main(String[] args) { + for(int t = 0; t < 20; ++t) { + Collection arrs = getRandomCollection(1, 5, 5); + Iterator var3 = arrs.iterator(); + + while(var3.hasNext()) { + int i = (Integer)var3.next(); + System.out.println(i); + } + + System.out.println("----"); + } + + } + + public static String getStringByUUID() { + return UUID.randomUUID().toString(); + } + + public static int[] getRandomArray(int min, int max, int n) { + int len = max - min + 1; + if (max >= min && n <= len) { + int[] source = new int[len]; + + for(int i = min; i < min + len; source[i - min] = i++) { + ; + } + + int[] result = new int[n]; + Random rd = new Random(); + + for(int i = 0; i < result.length; ++i) { + int index = Math.abs(rd.nextInt() % len--); + result[i] = source[index]; + source[index] = source[len]; + } + + return result; + } else { + return null; + } + } + + public static Collection getRandomCollection(int min, int max, int n) { + Set res = new HashSet(); + int mx = max; + int mn = min; + int i; + if (n == max + 1 - min) { + for(i = 1; i <= n; ++i) { + res.add(i); + } + + return res; + } else { + for(i = 0; i < n; ++i) { + int v = getIntegerBetween(mn, mx); + if (v == mx) { + --mx; + } + + if (v == mn) { + ++mn; + } + + while(res.contains(v)) { + v = getIntegerBetween(mn, mx); + if (v == mx) { + mx = v; + } + + if (v == mn) { + mn = v; + } + } + + res.add(v); + } + + return res; + } + } +} diff --git a/test/src/test/java/com/alibaba/nacos/test/naming/RegisterInstance_ITCase.java b/test/src/test/java/com/alibaba/nacos/test/naming/RegisterInstance_ITCase.java new file mode 100644 index 00000000000..7c61ef91015 --- /dev/null +++ b/test/src/test/java/com/alibaba/nacos/test/naming/RegisterInstance_ITCase.java @@ -0,0 +1,153 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.test.naming; + +import com.alibaba.nacos.api.naming.NamingFactory; +import com.alibaba.nacos.api.naming.NamingService; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.naming.NamingApp; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static com.alibaba.nacos.test.naming.NamingBase.*; + +/** + * Created by wangtong.wt on 2018/6/20. + * + * @author wangtong.wt + * @date 2018/6/20 + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = NamingApp.class, properties = {"server.servlet.context-path=/nacos"}, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class RegisterInstance_ITCase { + + private NamingService naming; + private NamingService naming2; + @LocalServerPort + private int port; + @Before + public void init() throws Exception{ + if (naming == null) { + TimeUnit.SECONDS.sleep(10); + naming = NamingFactory.createNamingService("127.0.0.1"+":"+port); + } + } + + /** + * 注册一个默认cluster的Instance,并验证 + * @throws Exception + */ + @Test + public void regDomTest() throws Exception{ + String serviceName = randomDomainName(); + + naming.registerInstance(serviceName, TEST_IP_4_DOM_1, TEST_PORT); + + TimeUnit.SECONDS.sleep(3); + + List instances = naming.getAllInstances(serviceName); + + Assert.assertEquals(instances.size(), 1); + Assert.assertTrue(instances.get(0).getInstanceId().contains(serviceName)); + //Assert.assertEquals(instances.get(0).getService().getName(), serviceName); + Assert.assertEquals(instances.get(0).getIp(), TEST_IP_4_DOM_1); + Assert.assertEquals(instances.get(0).getPort(), TEST_PORT); + } + + /** + * 注册一个自定义cluster的Instance,并验证 + * @throws Exception + */ + @Test + public void regDomClusterTest() throws Exception{ + String serviceName = randomDomainName(); + + naming.registerInstance(serviceName, TEST_IP_4_DOM_1, TEST_PORT, TEST_NEW_CLUSTER_4_DOM_1); + + TimeUnit.SECONDS.sleep(3); + + List instances = naming.getAllInstances(serviceName); + + Assert.assertEquals(instances.size(), 1); + Assert.assertTrue(instances.get(0).getInstanceId().contains(serviceName)); + //Assert.assertEquals(instances2.get(0).getService().getName(), serviceName); + Assert.assertEquals(instances.get(0).getIp(), TEST_IP_4_DOM_1); + Assert.assertEquals(instances.get(0).getPort(), TEST_PORT); + //Assert.assertEquals(instances.get(0).getCluster().getName(), TEST_NEW_CLUSTER_4_DOM_1); + + List instances2 = naming.getAllInstances(serviceName, Arrays.asList(TEST_NEW_CLUSTER_4_DOM_1)); + + Assert.assertEquals(instances2.size(), 1); + Assert.assertTrue(instances2.get(0).getInstanceId().contains(serviceName)); + //Assert.assertEquals(instances2.get(0).getService().getName(), serviceName); + Assert.assertEquals(instances2.get(0).getIp(), TEST_IP_4_DOM_1); + Assert.assertEquals(instances2.get(0).getPort(), TEST_PORT); + //Assert.assertEquals(instances2.get(0).getCluster().getName(), TEST_NEW_CLUSTER_4_DOM_1); + } + + /** + * 注册一个自定义的Instance,并验证 + * @throws Exception + */ + @Test + public void regDomWithInstance() throws Exception { + String serviceName = randomDomainName(); + + Instance i1 = getInstance(serviceName); + naming.registerInstance(serviceName, i1); + + TimeUnit.SECONDS.sleep(3); + + List instances = naming.getAllInstances(serviceName); + + Assert.assertEquals(instances.size(), 1); + + Assert.assertTrue(verifyInstance(i1, instances.get(0))); + + } + + /** + * 注册一个不健康的Instance,并验证 + * @throws Exception + */ + @Test + @Ignore + public void regDomNotHealth() throws Exception { + String serviceName = randomDomainName(); + System.out.println(serviceName); + + naming.registerInstance(serviceName, "1.1.1.1", 2000); + naming.registerInstance(serviceName, TEST_IP_4_DOM_1, TEST_PORT); + + TimeUnit.SECONDS.sleep(3); + + List instances = naming.selectInstances(serviceName,false); + + Assert.assertEquals(instances.size(), 1); + Assert.assertEquals(instances.get(0).isHealthy(), false); + } +} diff --git a/test/src/test/java/com/alibaba/nacos/test/naming/RestAPI_ITCase.java b/test/src/test/java/com/alibaba/nacos/test/naming/RestAPI_ITCase.java new file mode 100644 index 00000000000..fc489b7dbba --- /dev/null +++ b/test/src/test/java/com/alibaba/nacos/test/naming/RestAPI_ITCase.java @@ -0,0 +1,683 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.test.naming; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.nacos.naming.NamingApp; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.util.MultiValueMap; +import org.springframework.web.util.UriComponentsBuilder; + +import java.net.URL; + +import static org.junit.Assert.assertTrue; + +/** + * @author dungu.zpf + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = NamingApp.class, properties = {"server.servlet.context-path=/nacos", + "server.port=7001"}, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class RestAPI_ITCase { + + @LocalServerPort + private int port; + + private URL base; + + @Autowired + private TestRestTemplate restTemplate; + + @Before + public void setUp() throws Exception { + String url = String.format("http://localhost:%d/", port); + this.base = new URL(url); + prepareData(); + } + + @After + public void cleanup() throws Exception { + removeData(); + } + + @Test + public void dom() throws Exception { + + ResponseEntity response = request("/nacos/naming/api/dom", + Params.newParams().appendParam("dom", NamingBase.TEST_DOM_1).done(), String.class); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + JSONObject json = JSON.parseObject(response.getBody()); + + Assert.assertEquals(NamingBase.TEST_DOM_1, json.getString("name")); + } + + @Test + public void domCount() throws Exception { + + ResponseEntity response = request("/nacos/naming/api/domCount", + Params.newParams().done(), String.class); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + } + + @Test + public void rt4Dom() throws Exception { + + ResponseEntity response = request("/nacos/naming/api/rt4Dom", + Params.newParams().appendParam("dom", NamingBase.TEST_DOM_1).done(), String.class); + + assertTrue(response.getStatusCode().is2xxSuccessful()); + + } + + @Test + public void ip4Dom2() throws Exception { + + ResponseEntity response = request("/nacos/naming/api/ip4Dom2", + Params.newParams().appendParam("dom", NamingBase.TEST_DOM_1).done(), String.class); + + assertTrue(response.getStatusCode().is2xxSuccessful()); + + JSONObject json = JSON.parseObject(response.getBody()); + + Assert.assertNotNull(json.getJSONArray("ips")); + Assert.assertEquals(1, json.getJSONArray("ips").size()); + Assert.assertEquals(NamingBase.TEST_IP_4_DOM_1 + ":" + NamingBase.TEST_PORT_4_DOM_1, + json.getJSONArray("ips").getString(0).split("_")[0]); + + } + + @Test + public void ip4Dom() throws Exception { + + ResponseEntity response = request("/nacos/naming/api/ip4Dom", + Params.newParams().appendParam("dom", NamingBase.TEST_DOM_1).done(), String.class); + + assertTrue(response.getStatusCode().is2xxSuccessful()); + + JSONObject json = JSON.parseObject(response.getBody()); + + Assert.assertNotNull(json.getJSONArray("ips")); + Assert.assertEquals(1, json.getJSONArray("ips").size()); + Assert.assertEquals(NamingBase.TEST_IP_4_DOM_1, json.getJSONArray("ips").getJSONObject(0).getString("ip")); + Assert.assertEquals(NamingBase.TEST_PORT_4_DOM_1, json.getJSONArray("ips").getJSONObject(0).getString("port")); + + } + + @Test + public void replaceDom() throws Exception { + + ResponseEntity response = request("/nacos/naming/api/replaceDom", + Params.newParams() + .appendParam("dom", NamingBase.TEST_DOM_1) + .appendParam("token", NamingBase.TEST_TOKEN_4_DOM_1) + .appendParam("protectThreshold", "0.5") + .appendParam("enableHealthCheck", "false") + .appendParam("cktype", "HTTP") + .appendParam("ipPort4Check", "false") + .appendParam("path", "/hello") + .appendParam("headers", "1.1.1.1") + .appendParam("defCkport", "8080") + .appendParam("defIPPort", "8888") + .done(), String.class); + + assertTrue(response.getStatusCode().is2xxSuccessful()); + + response = request("/nacos/naming/api/dom", + Params.newParams().appendParam("dom", NamingBase.TEST_DOM_1).done(), String.class); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + JSONObject json = JSON.parseObject(response.getBody()); + + Assert.assertEquals(NamingBase.TEST_DOM_1, json.getString("name")); + Assert.assertEquals("0.5", json.getString("protectThreshold")); + Assert.assertEquals(NamingBase.TEST_TOKEN_4_DOM_1, json.getString("token")); + Assert.assertEquals("false", json.getString("enableHealthCheck")); + + JSONArray clusters = json.getJSONArray("clusters"); + Assert.assertNotNull(clusters); + Assert.assertEquals(1, clusters.size()); + Assert.assertEquals(false, clusters.getJSONObject(0).getBooleanValue("useIPPort4Check")); + Assert.assertEquals(8888, clusters.getJSONObject(0).getIntValue("defIPPort")); + Assert.assertEquals(8080, clusters.getJSONObject(0).getIntValue("defCkport")); + + } + + @Test + public void regAndDeregService() throws Exception { + + ResponseEntity response = request("/nacos/naming/api/regService", + Params.newParams() + .appendParam("dom", NamingBase.TEST_DOM_2) + .appendParam("app", "test1") + .appendParam("ip", NamingBase.TEST_IP_4_DOM_2) + .appendParam("port", NamingBase.TEST_PORT_4_DOM_2) + .appendParam("cluster", "DEFAULT") + .appendParam("token", NamingBase.TETS_TOKEN_4_DOM_2) + .done(), String.class); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + response = request("/nacos/naming/api/deRegService", + Params.newParams() + .appendParam("dom", NamingBase.TEST_DOM_2) + .appendParam("ip", NamingBase.TEST_IP_4_DOM_2) + .appendParam("port", NamingBase.TEST_PORT_4_DOM_2) + .appendParam("cluster", "DEFAULT") + .appendParam("token", NamingBase.TETS_TOKEN_4_DOM_2) + .done(), String.class); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + } + + @Test + public void updateDom() throws Exception { + + ResponseEntity response = request("/nacos/naming/api/updateDom", + Params.newParams() + .appendParam("dom", NamingBase.TEST_DOM_1) + .appendParam("token", NamingBase.TEST_TOKEN_4_DOM_1) + .appendParam("protectThreshold", "0.8") + .appendParam("enableHealthCheck", "false") + .appendParam("cktype", "TCP") + .appendParam("ipPort4Check", "false") + .appendParam("defCkPort", "10000") + .appendParam("defIPPort", "20000") + .done(), String.class); + + assertTrue(response.getStatusCode().is2xxSuccessful()); + + response = request("/nacos/naming/api/dom", + Params.newParams().appendParam("dom", NamingBase.TEST_DOM_1).done(), String.class); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + JSONObject json = JSON.parseObject(response.getBody()); + + Assert.assertEquals(NamingBase.TEST_DOM_1, json.getString("name")); + Assert.assertEquals("0.8", json.getString("protectThreshold")); + Assert.assertEquals("false", json.getString("enableHealthCheck")); + + JSONArray clusters = json.getJSONArray("clusters"); + Assert.assertNotNull(clusters); + Assert.assertEquals(1, clusters.size()); + Assert.assertEquals(false, clusters.getJSONObject(0).getBooleanValue("useIPPort4Check")); + Assert.assertEquals(20000, clusters.getJSONObject(0).getIntValue("defIPPort")); + Assert.assertEquals(10000, clusters.getJSONObject(0).getIntValue("defCkport")); + + } + + @Test + public void hello() throws Exception { + + ResponseEntity response = request("/nacos/naming/api/hello", + Params.newParams().done(), String.class); + + assertTrue(response.getStatusCode().is2xxSuccessful()); + } + + @Test + public void replaceIP4Dom() throws Exception { + + ResponseEntity response = request("/nacos/naming/api/replaceIP4Dom", + Params.newParams() + .appendParam("dom", NamingBase.TEST_DOM_1) + .appendParam("cluster", "DEFAULT") + .appendParam("ipList", NamingBase.TEST_IP_4_DOM_1 + ":" + NamingBase.TEST_PORT2_4_DOM_1) + .appendParam("token", NamingBase.TEST_TOKEN_4_DOM_1) + .done(), String.class); + + assertTrue(response.getStatusCode().is2xxSuccessful()); + + response = request("/nacos/naming/api/ip4Dom2", + Params.newParams().appendParam("dom", NamingBase.TEST_DOM_1).done(), String.class); + + assertTrue(response.getStatusCode().is2xxSuccessful()); + + JSONObject json = JSON.parseObject(response.getBody()); + + Assert.assertNotNull(json.getJSONArray("ips")); + Assert.assertEquals(1, json.getJSONArray("ips").size()); + Assert.assertEquals(NamingBase.TEST_IP_4_DOM_1 + ":" + NamingBase.TEST_PORT2_4_DOM_1, + json.getJSONArray("ips").getString(0).split("_")[0]); + + } + + @Test + public void srvAllIP() throws Exception { + + ResponseEntity response = request("/nacos/naming/api/srvAllIP", + Params.newParams() + .appendParam("dom", NamingBase.TEST_DOM_1) + .done(), String.class); + + assertTrue(response.getStatusCode().is2xxSuccessful()); + + JSONObject json = JSON.parseObject(response.getBody()); + + Assert.assertEquals(NamingBase.TEST_DOM_1, json.getString("dom")); + JSONArray hosts = json.getJSONArray("hosts"); + Assert.assertNotNull(hosts); + Assert.assertEquals(1, hosts.size()); + Assert.assertEquals(NamingBase.TEST_IP_4_DOM_1, hosts.getJSONObject(0).getString("ip")); + Assert.assertEquals(NamingBase.TEST_PORT_4_DOM_1, hosts.getJSONObject(0).getString("port")); + } + + @Test + public void srvIPXT() throws Exception { + + ResponseEntity response = request("/nacos/naming/api/srvIPXT", + Params.newParams() + .appendParam("dom", NamingBase.TEST_DOM_1) + .done(), String.class); + + assertTrue(response.getStatusCode().is2xxSuccessful()); + + JSONObject json = JSON.parseObject(response.getBody()); + + Assert.assertEquals(NamingBase.TEST_DOM_1, json.getString("dom")); + JSONArray hosts = json.getJSONArray("hosts"); + Assert.assertNotNull(hosts); + Assert.assertEquals(1, hosts.size()); + Assert.assertEquals(NamingBase.TEST_IP_4_DOM_1, hosts.getJSONObject(0).getString("ip")); + Assert.assertEquals(NamingBase.TEST_PORT_4_DOM_1, hosts.getJSONObject(0).getString("port")); + } + + @Test + public void remvIP4Dom() throws Exception { + + + ResponseEntity response = request("/nacos/naming/api/addIP4Dom", + Params.newParams() + .appendParam("dom", NamingBase.TEST_DOM_1) + .appendParam("ipList", NamingBase.TEST_IP_4_DOM_1 + ":" + NamingBase.TEST_PORT2_4_DOM_1) + .appendParam("token", NamingBase.TEST_TOKEN_4_DOM_1).done(), + String.class); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + response = request("/nacos/naming/api/remvIP4Dom", + Params.newParams() + .appendParam("dom", NamingBase.TEST_DOM_1) + .appendParam("ipList", NamingBase.TEST_IP_4_DOM_1 + ":" + NamingBase.TEST_PORT2_4_DOM_1) + .appendParam("token", NamingBase.TEST_TOKEN_4_DOM_1).done(), + String.class); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + } + + @Test + public void updateSwitch() throws Exception { + + ResponseEntity response = request("/nacos/naming/api/updateSwitch", + Params.newParams() + .appendParam("entry", "distroThreshold") + .appendParam("distroThreshold", "0.3") + .appendParam("token", "xy").done(), + String.class); + + assertTrue(response.getStatusCode().is2xxSuccessful()); + + response = request("/nacos/naming/api/updateSwitch", + Params.newParams() + .appendParam("entry", "enableAllDomNameCache") + .appendParam("enableAllDomNameCache", "false") + .appendParam("token", "xy").done(), + String.class); + + assertTrue(response.getStatusCode().is2xxSuccessful()); + + response = request("/nacos/naming/api/updateSwitch", + Params.newParams() + .appendParam("entry", "incrementalList") + .appendParam("incrementalList", "1.com,2.com") + .appendParam("action", "update") + .appendParam("token", "xy").done(), + String.class); + + assertTrue(response.getStatusCode().is2xxSuccessful()); + + response = request("/nacos/naming/api/updateSwitch", + Params.newParams() + .appendParam("entry", "healthCheckWhiteList") + .appendParam("healthCheckWhiteList", "1.com,2.com") + .appendParam("action", "update") + .appendParam("token", "xy").done(), + String.class); + + assertTrue(response.getStatusCode().is2xxSuccessful()); + + response = request("/nacos/naming/api/updateSwitch", + Params.newParams() + .appendParam("entry", "clientBeatInterval") + .appendParam("clientBeatInterval", "5000") + .appendParam("token", "xy").done(), + String.class); + + assertTrue(response.getStatusCode().is2xxSuccessful()); + + response = request("/nacos/naming/api/updateSwitch", + Params.newParams() + .appendParam("entry", "pushVersion") + .appendParam("type", "java") + .appendParam("version", "4.0.0") + .appendParam("token", "xy").done(), + String.class); + + assertTrue(response.getStatusCode().is2xxSuccessful()); + + response = request("/nacos/naming/api/updateSwitch", + Params.newParams() + .appendParam("entry", "pushCacheMillis") + .appendParam("millis", "30000") + .appendParam("token", "xy").done(), + String.class); + + assertTrue(response.getStatusCode().is2xxSuccessful()); + + response = request("/nacos/naming/api/updateSwitch", + Params.newParams() + .appendParam("entry", "defaultCacheMillis") + .appendParam("millis", "3000") + .appendParam("token", "xy").done(), + String.class); + + assertTrue(response.getStatusCode().is2xxSuccessful()); + + response = request("/nacos/naming/api/switches", + Params.newParams().done(), + String.class); + + assertTrue(response.getStatusCode().is2xxSuccessful()); + + JSONObject switches = JSON.parseObject(response.getBody()); + + System.out.println(switches); + + Assert.assertEquals("0.3", switches.getString("distroThreshold")); + Assert.assertEquals("false", switches.getString("allDomNameCache")); + Assert.assertTrue(switches.getJSONArray("incrementalList").contains("1.com")); + Assert.assertTrue(switches.getJSONArray("incrementalList").contains("2.com")); + Assert.assertTrue(switches.getJSONArray("healthCheckWhiteList").contains("1.com")); + Assert.assertTrue(switches.getJSONArray("healthCheckWhiteList").contains("2.com")); + Assert.assertEquals("5000", switches.getString("clientBeatInterval")); + Assert.assertEquals("4.0.0", switches.getString("pushJavaVersion")); + Assert.assertEquals("30000", switches.getString("defaultPushCacheMillis")); + Assert.assertEquals("3000", switches.getString("defaultCacheMillis")); + } + + @Test + public void checkStatus() throws Exception { + + ResponseEntity response = request("/nacos/naming/api/checkStatus", + Params.newParams().done(), + String.class); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + } + + @Test + public void allDomNames() throws Exception { + + ResponseEntity response = request("/nacos/naming/api/allDomNames", + Params.newParams().done(), + String.class); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + JSONObject json = JSON.parseObject(response.getBody()); + + Assert.assertEquals(json.getIntValue("count"), json.getJSONArray("doms").size()); + } + + + @Test + public void searchDom() throws Exception { + + ResponseEntity response = request("/nacos/naming/api/searchDom", + Params.newParams() + .appendParam("expr", "nacos") + .done(), + String.class); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + JSONObject json = JSON.parseObject(response.getBody()); + Assert.assertTrue(json.getJSONArray("doms").size() > 0); + } + + @Test + public void addCluster4Dom() throws Exception { + + ResponseEntity response = request("/nacos/naming/api/addCluster4Dom", + Params.newParams() + .appendParam("dom", NamingBase.TEST_DOM_1) + .appendParam("token", NamingBase.TEST_TOKEN_4_DOM_1) + .appendParam("clusterName", NamingBase.TEST_NEW_CLUSTER_4_DOM_1) + .appendParam("cktype", "TCP") + .appendParam("defIPPort", "1111") + .appendParam("defCkport", "2222") + .done(), + String.class); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + response = request("/nacos/naming/api/dom", + Params.newParams().appendParam("dom", NamingBase.TEST_DOM_1).done(), String.class); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + Assert.assertTrue(response.getBody().contains(NamingBase.TEST_NEW_CLUSTER_4_DOM_1)); + + JSONObject json = JSON.parseObject(response.getBody()); + + Assert.assertEquals(NamingBase.TEST_DOM_1, json.getString("name")); + + JSONArray clusters = json.getJSONArray("clusters"); + Assert.assertEquals(2, clusters.size()); + for (int i=0; i<2; i++) { + JSONObject cluster = clusters.getJSONObject(i); + if (cluster.getString("name").equals(NamingBase.TEST_NEW_CLUSTER_4_DOM_1)) { + + Assert.assertEquals("1111", cluster.getString("defIPPort")); + Assert.assertEquals("2222", cluster.getString("defCkport")); + + } + } + } + + @Test + public void domList() throws Exception { + + ResponseEntity response = request("/nacos/naming/api/domList", + Params.newParams() + .appendParam("startPg", "0") + .appendParam("pgSize", "10") + .done(), + String.class); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + JSONObject json = JSON.parseObject(response.getBody()); + + Assert.assertTrue(json.getJSONArray("domList").size() > 0); + } + + @Test + public void distroStatus() throws Exception { + + ResponseEntity response = request("/nacos/naming/api/distroStatus", + Params.newParams() + .done(), + String.class); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + } + + @Test + public void metrics() throws Exception { + + ResponseEntity response = request("/nacos/naming/api/metrics", + Params.newParams() + .done(), + String.class); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + JSONObject json = JSON.parseObject(response.getBody()); + Assert.assertTrue(json.getIntValue("domCount") > 0); + Assert.assertTrue(json.getIntValue("ipCount") > 0); + Assert.assertTrue(json.getIntValue("responsibleDomCount") > 0); + Assert.assertTrue(json.getIntValue("responsibleIPCount") > 0); + } + + @Test + public void updateClusterConf() throws Exception { + // TODO + } + + @Test + public void reCalculateCheckSum4Dom() throws Exception { + + ResponseEntity response = request("/nacos/naming/api/reCalculateCheckSum4Dom", + Params.newParams() + .appendParam("dom", NamingBase.TEST_DOM_1) + .done(), + String.class); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + } + + @Test + public void getDomString4MD5() throws Exception { + + ResponseEntity response = request("/nacos/naming/api/getDomString4MD5", + Params.newParams() + .appendParam("dom", NamingBase.TEST_DOM_1) + .done(), + String.class); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + } + + @Test + public void getResponsibleServer4Dom() throws Exception { + + ResponseEntity response = request("/nacos/naming/api/getResponsibleServer4Dom", + Params.newParams() + .appendParam("dom", NamingBase.TEST_DOM_1) + .done(), + String.class); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + } + + @Test + public void domServeStatus() throws Exception { + + ResponseEntity response = request("/nacos/naming/api/domServeStatus", + Params.newParams() + .appendParam("dom", NamingBase.TEST_DOM_1) + .done(), + String.class); + + Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); + + JSONObject json = JSON.parseObject(response.getBody()); + Assert.assertTrue(json.getBooleanValue("success")); + Assert.assertTrue(json.getJSONObject("data").getJSONArray("ips").size() > 0); + } + + private ResponseEntity request(String path, MultiValueMap params, Class clazz) { + + HttpHeaders headers = new HttpHeaders(); + + HttpEntity entity = new HttpEntity<>(headers); + + UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(this.base.toString() + path) + .queryParams(params); + + return this.restTemplate.exchange( + builder.toUriString(), HttpMethod.GET, entity, clazz); + } + + private void prepareData() { + + ResponseEntity responseEntity = request("/nacos/naming/api/regDom", + Params.newParams() + .appendParam("dom", NamingBase.TEST_DOM_1) + .appendParam("cktype", "TCP") + .appendParam("token", "abc") + .done(), + String.class); + + if (responseEntity.getStatusCode().isError()) { + throw new RuntimeException("before test: register domain failed!" + responseEntity.toString()); + } + + try { + Thread.sleep(100L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + responseEntity = request("/nacos/naming/api/addIP4Dom", + Params.newParams() + .appendParam("dom", NamingBase.TEST_DOM_1) + .appendParam("ipList", NamingBase.TEST_IP_4_DOM_1 + ":" + NamingBase.TEST_PORT_4_DOM_1) + .appendParam("token", NamingBase.TEST_TOKEN_4_DOM_1).done(), + String.class); + + if (responseEntity.getStatusCode().isError()) { + throw new RuntimeException("before test: add ip for domain failed!" + responseEntity.toString()); + } + } + + private void removeData() { + + ResponseEntity responseEntity = request("/nacos/naming/api/remvDom", + Params.newParams() + .appendParam("dom", NamingBase.TEST_DOM_1) + .appendParam("token", "abc") + .done(), + String.class); + + if (responseEntity.getStatusCode().isError()) { + throw new RuntimeException("before test: remove domain failed!" + responseEntity.toString()); + } + } + +} diff --git a/test/src/test/java/com/alibaba/nacos/test/naming/SelectInstances_ITCase.java b/test/src/test/java/com/alibaba/nacos/test/naming/SelectInstances_ITCase.java new file mode 100644 index 00000000000..7beecc08c1e --- /dev/null +++ b/test/src/test/java/com/alibaba/nacos/test/naming/SelectInstances_ITCase.java @@ -0,0 +1,153 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.test.naming; + +import com.alibaba.nacos.api.naming.NamingFactory; +import com.alibaba.nacos.api.naming.NamingService; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.naming.NamingApp; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static com.alibaba.nacos.test.naming.NamingBase.*; + +/** + * Created by wangtong.wt on 2018/6/20. + * + * @author wangtong.wt + * @date 2018/6/20 + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = NamingApp.class, properties = {"server.servlet.context-path=/nacos"}, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class SelectInstances_ITCase { + + private NamingService naming; + @LocalServerPort + private int port; + @Before + public void init() throws Exception{ + if (naming == null) { + TimeUnit.SECONDS.sleep(10); + naming = NamingFactory.createNamingService("127.0.0.1"+":"+port); + } + } + + /** + * 获取所有健康的Instance + * @throws Exception + */ + @Test + @Ignore + public void selectHealthyInstances() throws Exception { + String serviceName = randomDomainName(); + naming.registerInstance(serviceName, "127.0.0.1", TEST_PORT); + naming.registerInstance(serviceName, "1.1.1.1", 9090); + + TimeUnit.SECONDS.sleep(10); + + List instances = naming.selectInstances(serviceName, true); + + Assert.assertEquals(instances.size(), 1); + + + Instance instanceNotH = null; + List instancesGet = naming.getAllInstances(serviceName); + for (Instance instance : instancesGet) { + if (!instance.isHealthy()) { + instanceNotH = instance; + } + } + + instancesGet.remove(instanceNotH); + + Assert.assertTrue(verifyInstanceList(instances, instancesGet)); + } + + /** + * 获取所有不健康的Instance + * @throws Exception + */ + @Test + @Ignore + public void selectUnhealthyInstances() throws Exception { + String serviceName = randomDomainName(); + naming.registerInstance(serviceName, "1.1.1.1", TEST_PORT); + naming.registerInstance(serviceName, "1.1.1.2", TEST_PORT); + + TimeUnit.SECONDS.sleep(8); + List instances = naming.selectInstances(serviceName, false); + + TimeUnit.SECONDS.sleep(2); + Assert.assertEquals(instances.size(), 2); + + List instancesGet = naming.getAllInstances(serviceName); + + Assert.assertTrue(verifyInstanceList(instances, instancesGet)); + } + + /** + * 获取指定cluster中(单个、多个)所有健康的Instance + * @throws Exception + */ + @Test + @Ignore + public void selectHealthyInstancesClusters() throws Exception { + String serviceName = randomDomainName(); + naming.registerInstance(serviceName, "127.0.0.1", TEST_PORT, "c1"); + naming.registerInstance(serviceName, "127.0.0.2", 9090, "c2"); + + TimeUnit.SECONDS.sleep(8); + List instances = naming.selectInstances(serviceName, Arrays.asList("c1", "c2"), true); + TimeUnit.SECONDS.sleep(2); + Assert.assertEquals(instances.size(), 2); + + List instancesGet = naming.getAllInstances(serviceName); + + Assert.assertTrue(verifyInstanceList(instances, instancesGet)); + } + + /** + * 获取指定cluster中(单个、多个)不所有健康的Instance + * @throws Exception + */ + @Test + @Ignore + public void selectUnhealthyInstancesClusters() throws Exception { + String serviceName = randomDomainName(); + naming.registerInstance(serviceName, "1.1.1.1", TEST_PORT, "c1"); + naming.registerInstance(serviceName, "1.1.1.2", TEST_PORT, "c2"); + + TimeUnit.SECONDS.sleep(8); + List instances = naming.selectInstances(serviceName, Arrays.asList("c1", "c2"), false); + TimeUnit.SECONDS.sleep(2); + Assert.assertEquals(instances.size(), 2); + + List instancesGet = naming.getAllInstances(serviceName); + + Assert.assertTrue(verifyInstanceList(instances, instancesGet)); + } +} diff --git a/test/src/test/java/com/alibaba/nacos/test/naming/SelectOneHealthyInstance_ITCase.java b/test/src/test/java/com/alibaba/nacos/test/naming/SelectOneHealthyInstance_ITCase.java new file mode 100644 index 00000000000..ec6746bfa76 --- /dev/null +++ b/test/src/test/java/com/alibaba/nacos/test/naming/SelectOneHealthyInstance_ITCase.java @@ -0,0 +1,168 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.test.naming; + +import com.alibaba.nacos.api.naming.NamingFactory; +import com.alibaba.nacos.api.naming.NamingService; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.naming.NamingApp; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.test.context.junit4.SpringRunner; + +import java.net.ServerSocket; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; +import static com.alibaba.nacos.test.naming.NamingBase.*; + +/** + * Created by wangtong.wt on 2018/6/20. + * + * @author wangtong.wt + * @date 2018/6/20 + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = NamingApp.class, properties = {"server.servlet.context-path=/nacos"}, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class SelectOneHealthyInstance_ITCase { + + private NamingService naming; + private ServerSocket localServer1 = null; + private ServerSocket localServer2 = null; + private ServerSocket localServer3 = null; + @LocalServerPort + private int port; + @Before + public void init() throws Exception{ + if (naming == null) { + TimeUnit.SECONDS.sleep(10); + naming = NamingFactory.createNamingService("127.0.0.1"+":"+port); + } + + localServer1 = new ServerSocket(60000); + localServer2 = new ServerSocket(60001); + localServer3 = new ServerSocket(60002); + } + + @After + public void stopLocalServer() throws Exception{ + if (localServer1 != null) { + localServer1.close(); + } + if (localServer2 != null) { + localServer2.close(); + } + if (localServer3 != null) { + localServer3.close(); + } + } + + /** + * 获取一个健康的Instance + * @throws Exception + */ + @Test + public void selectOneHealthyInstances() throws Exception { + String serviceName = randomDomainName(); + naming.registerInstance(serviceName, "127.0.0.1", TEST_PORT); + naming.registerInstance(serviceName, "127.0.0.1", 60000); + + TimeUnit.SECONDS.sleep(2); + Instance instance = naming.selectOneHealthyInstance(serviceName); + + List instancesGet = naming.getAllInstances(serviceName); + + for (Instance instance1 : instancesGet) { + if (instance1.getIp().equals(instance.getIp())&& + instance1.getPort() == instance.getPort()) { + Assert.assertTrue(instance.isHealthy()); + Assert.assertTrue(verifyInstance(instance1, instance)); + return; + } + } + + Assert.assertTrue(false); + } + + /** + * 获取指定单个cluster中一个健康的Instance + * @throws Exception + */ + @Test + public void selectOneHealthyInstancesCluster() throws Exception { + String serviceName = randomDomainName(); + naming.registerInstance(serviceName, "127.0.0.1", TEST_PORT, "c1"); + naming.registerInstance(serviceName, "127.0.0.1", 60000, "c1"); + naming.registerInstance(serviceName, "1.1.1.1", TEST_PORT, "c1"); + naming.registerInstance(serviceName, "127.0.0.1", 60001, "c1"); + naming.registerInstance(serviceName, "127.0.0.1", 60002, "c2"); + + TimeUnit.SECONDS.sleep(2); + Instance instance = naming.selectOneHealthyInstance(serviceName, Arrays.asList("c1")); + + Assert.assertTrue(instance.getIp() != "1.1.1.1"); + Assert.assertTrue(instance.getPort() != 60002); + + List instancesGet = naming.getAllInstances(serviceName); + + for (Instance instance1 : instancesGet) { + if (instance1.getIp().equals(instance.getIp())&& + instance1.getPort() == instance.getPort()) { + Assert.assertTrue(instance.isHealthy()); + Assert.assertTrue(verifyInstance(instance1, instance)); + return; + } + } + + Assert.assertTrue(false); + } + + /** + * 获取指定多个cluster中一个健康的Instance + * @throws Exception + */ + @Test + public void selectOneHealthyInstancesClusters() throws Exception { + String serviceName = randomDomainName(); + naming.registerInstance(serviceName, "1.1.1.1", TEST_PORT, "c1"); + naming.registerInstance(serviceName, "127.0.0.1", TEST_PORT, "c1"); + naming.registerInstance(serviceName, "127.0.0.1", 60000, "c1"); + naming.registerInstance(serviceName, "127.0.0.1", 60001, "c2"); + + TimeUnit.SECONDS.sleep(2); + Instance instance = naming.selectOneHealthyInstance(serviceName, Arrays.asList("c1", "c2")); + Assert.assertTrue(instance.getIp() != "1.1.1.1"); + + List instancesGet = naming.getAllInstances(serviceName); + + for (Instance instance1 : instancesGet) { + if (instance1.getIp().equals(instance.getIp()) && + instance1.getPort() == instance.getPort()) { + Assert.assertTrue(instance.isHealthy()); + Assert.assertTrue(verifyInstance(instance1, instance)); + return; + } + } + + Assert.assertTrue(false); + } +} diff --git a/test/src/test/java/com/alibaba/nacos/test/naming/Starter_ITCase.java b/test/src/test/java/com/alibaba/nacos/test/naming/Starter_ITCase.java new file mode 100644 index 00000000000..4ce2b68d342 --- /dev/null +++ b/test/src/test/java/com/alibaba/nacos/test/naming/Starter_ITCase.java @@ -0,0 +1,22 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.test.naming; + +/** + * @author dungu.zpf + */ +public class Starter_ITCase { +} diff --git a/test/src/test/java/com/alibaba/nacos/test/naming/SubscribeCluster_ITCase.java b/test/src/test/java/com/alibaba/nacos/test/naming/SubscribeCluster_ITCase.java new file mode 100644 index 00000000000..61284891da1 --- /dev/null +++ b/test/src/test/java/com/alibaba/nacos/test/naming/SubscribeCluster_ITCase.java @@ -0,0 +1,222 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.test.naming; + +import com.alibaba.nacos.api.naming.NamingFactory; +import com.alibaba.nacos.api.naming.NamingService; +import com.alibaba.nacos.api.naming.listener.Event; +import com.alibaba.nacos.api.naming.listener.EventListener; +import com.alibaba.nacos.api.naming.listener.NamingEvent; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.naming.NamingApp; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static com.alibaba.nacos.test.naming.NamingBase.*; + +/** + * Created by wangtong.wt on 2018/6/20. + * + * @author wangtong.wt + * @date 2018/6/20 + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = NamingApp.class, properties = {"server.servlet.context-path=/nacos"}, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class SubscribeCluster_ITCase { + + private NamingService naming; + @LocalServerPort + private int port; + @Before + public void init() throws Exception{ + instances.clear(); + if (naming == null) { + TimeUnit.SECONDS.sleep(10); + naming = NamingFactory.createNamingService("127.0.0.1"+":"+port); + } + } + + private volatile List instances = Collections.emptyList(); + + /** + * 添加IP,收到通知 + * @throws Exception + */ + @Test + public void subscribeAdd() throws Exception { + String serviceName = randomDomainName(); + + naming.subscribe(serviceName, Arrays.asList("c1"), new EventListener() { + @Override + public void onEvent(Event event) { + System.out.println(((NamingEvent)event).getServiceName()); + System.out.println(((NamingEvent)event).getInstances()); + instances = ((NamingEvent)event).getInstances(); + } + }); + + naming.registerInstance(serviceName, "127.0.0.1", TEST_PORT, "c1"); + + while (instances.isEmpty()) { + Thread.sleep(1000L); + } + + Assert.assertTrue(verifyInstanceList(instances, naming.getAllInstances(serviceName))); + } + + /** + * 删除IP,收到通知 + * @throws Exception + */ + @Test + public void subscribeDelete() throws Exception { + String serviceName = randomDomainName(); + naming.registerInstance(serviceName, "127.0.0.1", TEST_PORT, "c1"); + naming.registerInstance(serviceName, "127.0.0.2", TEST_PORT, "c1"); + + TimeUnit.SECONDS.sleep(3); + + naming.subscribe(serviceName, Arrays.asList("c1"), new EventListener() { + int index = 0; + @Override + public void onEvent(Event event) { + if (index == 0) { + index++; + return; + } + System.out.println(((NamingEvent)event).getServiceName()); + System.out.println(((NamingEvent)event).getInstances()); + instances = ((NamingEvent)event).getInstances(); + } + }); + + naming.deregisterInstance(serviceName, "127.0.0.1", TEST_PORT, "c1"); + + while (instances.isEmpty()) { + Thread.sleep(1000L); + } + + Assert.assertTrue(verifyInstanceList(instances, naming.getAllInstances(serviceName))); + } + + /** + * 改变IP权重,收到通知 + * @throws Exception + */ + @Test + public void subscribeChangeWeight() throws Exception { + String serviceName = randomDomainName(); + Instance instance = getInstance(serviceName); + naming.registerInstance(serviceName, instance); + + TimeUnit.SECONDS.sleep(3); + + naming.subscribe(serviceName, Arrays.asList("c1"), new EventListener() { + int index = 0; + @Override + public void onEvent(Event event) { + if (index == 0) { + index++; + return; + } + System.out.println(((NamingEvent)event).getServiceName()); + System.out.println(((NamingEvent)event).getInstances()); + instances = ((NamingEvent)event).getInstances(); + } + }); + + instance.setWeight(66.0); + naming.registerInstance(serviceName, instance); + + while (instances.isEmpty()) { + Thread.sleep(1000L); + } + + Assert.assertTrue(verifyInstanceList(instances, naming.getAllInstances(serviceName))); + } + + /** + * 添加不可用IP,收到通知 + * @throws Exception + */ + @Test + public void subscribeUnhealthy() throws Exception { + String serviceName = randomDomainName(); + + naming.subscribe(serviceName, Arrays.asList("c1"), new EventListener() { + @Override + public void onEvent(Event event) { + System.out.println(((NamingEvent)event).getServiceName()); + System.out.println(((NamingEvent)event).getInstances()); + instances = ((NamingEvent)event).getInstances(); + } + }); + + naming.registerInstance(serviceName, "1.1.1.1", TEST_PORT, "c1"); + + while (instances.isEmpty()) { + Thread.sleep(1000L); + } + + Assert.assertTrue(verifyInstanceList(instances, naming.getAllInstances(serviceName))); + } + + /** + * 新增其他cluster IP,不会收到通知 + * @throws Exception + */ + @Test + public void subscribeOtherCluster() throws Exception { + String serviceName = randomDomainName(); + + naming.subscribe(serviceName, Arrays.asList("c2"), new EventListener() { + int index = 0; + @Override + public void onEvent(Event event) { + if (index == 0) { + index++; + return; + } + System.out.println(((NamingEvent)event).getServiceName()); + System.out.println(((NamingEvent)event).getInstances()); + instances = ((NamingEvent)event).getInstances(); + } + }); + + naming.registerInstance(serviceName, "1.1.1.1", TEST_PORT, "c1"); + + int i = 0; + while (instances.isEmpty()) { + Thread.sleep(1000L); + if (i++ > 20) { + return; + } + } + + Assert.assertTrue(false); + } +} diff --git a/test/src/test/java/com/alibaba/nacos/test/naming/Subscribe_ITCase.java b/test/src/test/java/com/alibaba/nacos/test/naming/Subscribe_ITCase.java new file mode 100644 index 00000000000..43a405606d2 --- /dev/null +++ b/test/src/test/java/com/alibaba/nacos/test/naming/Subscribe_ITCase.java @@ -0,0 +1,193 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.test.naming; + +import com.alibaba.nacos.api.naming.NamingFactory; +import com.alibaba.nacos.api.naming.NamingService; +import com.alibaba.nacos.api.naming.listener.Event; +import com.alibaba.nacos.api.naming.listener.EventListener; +import com.alibaba.nacos.api.naming.listener.NamingEvent; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.naming.NamingApp; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static com.alibaba.nacos.test.naming.NamingBase.*; + +/** + * Created by wangtong.wt on 2018/6/20. + * + * @author wangtong.wt + * @date 2018/6/20 + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = NamingApp.class, properties = {"server.servlet.context-path=/nacos"}, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class Subscribe_ITCase { + + private NamingService naming; + @LocalServerPort + private int port; + @Before + public void init() throws Exception{ + instances.clear(); + if (naming == null) { + TimeUnit.SECONDS.sleep(10); + naming = NamingFactory.createNamingService("127.0.0.1"+":"+port); + } + } + + private volatile List instances = Collections.emptyList(); + + /** + * 添加IP,收到通知 + * @throws Exception + */ + @Test + public void subscribeAdd() throws Exception { + String serviceName = randomDomainName(); + + naming.subscribe(serviceName, new EventListener() { + @Override + public void onEvent(Event event) { + System.out.println(((NamingEvent)event).getServiceName()); + System.out.println(((NamingEvent)event).getInstances()); + instances = ((NamingEvent)event).getInstances(); + } + }); + + naming.registerInstance(serviceName, "127.0.0.1", TEST_PORT, "c1"); + + while (instances.isEmpty()) { + Thread.sleep(1000L); + } + + Assert.assertTrue(verifyInstanceList(instances, naming.getAllInstances(serviceName))); + } + + /** + * 删除IP,收到通知 + * @throws Exception + */ + @Test + public void subscribeDelete() throws Exception { + String serviceName = randomDomainName(); + naming.registerInstance(serviceName, "127.0.0.1", TEST_PORT, "c1"); + naming.registerInstance(serviceName, "127.0.0.2", TEST_PORT, "c1"); + + TimeUnit.SECONDS.sleep(3); + + naming.subscribe(serviceName, new EventListener() { + int index = 0; + @Override + public void onEvent(Event event) { + if (index == 0) { + index++; + return; + } + System.out.println(((NamingEvent)event).getServiceName()); + System.out.println(((NamingEvent)event).getInstances()); + instances = ((NamingEvent)event).getInstances(); + } + }); + + naming.deregisterInstance(serviceName, "127.0.0.1", TEST_PORT, "c1"); + + while (instances.isEmpty()) { + Thread.sleep(1000L); + } + + Assert.assertTrue(verifyInstanceList(instances, naming.getAllInstances(serviceName))); + } + + /** + * 改变IP权重,收到通知 + * @throws Exception + */ + @Test + public void subscribeChangeWeight() throws Exception { + String serviceName = randomDomainName(); + Instance instance = getInstance(serviceName); + naming.registerInstance(serviceName, instance); + + TimeUnit.SECONDS.sleep(3); + + naming.subscribe(serviceName, new EventListener() { + int index = 0; + + @Override + public void onEvent(Event event) { + if (index == 0) { + index++; + return; + } + System.out.println(((NamingEvent)event).getServiceName()); + System.out.println(((NamingEvent)event).getInstances()); + instances = ((NamingEvent)event).getInstances(); + } + }); + + instance.setWeight(66.0); + naming.registerInstance(serviceName, instance); + + int index = 0; + while (instances.isEmpty()) { + System.out.println("等待接收推送"); + Thread.sleep(1000L); + if (index ++ == 30) { + System.out.println("30秒内没有接收到推送,失败"); + Assert.assertTrue(false); + } + } + + Assert.assertTrue(verifyInstanceList(instances, naming.getAllInstances(serviceName))); + } + + /** + * 添加不可用IP,收到通知 + * @throws Exception + */ + @Test + public void subscribeUnhealthy() throws Exception { + String serviceName = randomDomainName(); + + naming.subscribe(serviceName, new EventListener() { + @Override + public void onEvent(Event event) { + System.out.println(((NamingEvent)event).getServiceName()); + System.out.println(((NamingEvent)event).getInstances()); + instances = ((NamingEvent)event).getInstances(); + } + }); + + naming.registerInstance(serviceName, "1.1.1.1", TEST_PORT, "c1"); + + while (instances.isEmpty()) { + Thread.sleep(1000L); + } + + Assert.assertTrue(verifyInstanceList(instances, naming.getAllInstances(serviceName))); + } +} diff --git a/test/src/test/java/com/alibaba/nacos/test/naming/Unsubscribe_ITCase.java b/test/src/test/java/com/alibaba/nacos/test/naming/Unsubscribe_ITCase.java new file mode 100644 index 00000000000..8ae32b1d032 --- /dev/null +++ b/test/src/test/java/com/alibaba/nacos/test/naming/Unsubscribe_ITCase.java @@ -0,0 +1,152 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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 com.alibaba.nacos.test.naming; + +import com.alibaba.nacos.api.naming.NamingFactory; +import com.alibaba.nacos.api.naming.NamingService; +import com.alibaba.nacos.api.naming.listener.Event; +import com.alibaba.nacos.api.naming.listener.EventListener; +import com.alibaba.nacos.api.naming.listener.NamingEvent; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.naming.NamingApp; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static com.alibaba.nacos.test.naming.NamingBase.*; + +/** + * Created by wangtong.wt on 2018/6/20. + * + * @author wangtong.wt + * @date 2018/6/20 + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = NamingApp.class, properties = {"server.servlet.context-path=/nacos"}, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class Unsubscribe_ITCase { + + private NamingService naming; + @LocalServerPort + private int port; + + @Before + public void init() throws Exception{ + instances = Collections.emptyList(); + if (naming == null) { + TimeUnit.SECONDS.sleep(10); + naming = NamingFactory.createNamingService("127.0.0.1"+":"+port); + } + } + + private volatile List instances = Collections.emptyList(); + + /** + * 取消订阅,添加IP,不会收到通知 + * @throws Exception + */ + @Test + public void unsubscribe() throws Exception { + String serviceName = randomDomainName(); + + EventListener listener = new EventListener() { + @Override + public void onEvent(Event event) { + System.out.println(((NamingEvent)event).getServiceName()); + System.out.println(((NamingEvent)event).getInstances()); + instances = ((NamingEvent)event).getInstances(); + } + }; + + naming.subscribe(serviceName, listener); + + naming.registerInstance(serviceName, "127.0.0.1", TEST_PORT, "c1"); + + while (instances.isEmpty()) { + Thread.sleep(1000L); + } + + Assert.assertTrue(verifyInstanceList(instances, naming.getAllInstances(serviceName))); + + naming.unsubscribe(serviceName, listener); + + instances = Collections.emptyList(); + naming.registerInstance(serviceName, "127.0.0.2", TEST_PORT, "c1"); + + int i = 0; + while (instances.isEmpty()) { + Thread.sleep(1000L); + if (i++ > 20) { + return; + } + } + + Assert.assertTrue(false); + } + + /** + * 取消订阅,在指定cluster添加IP,不会收到通知 + * @throws Exception + */ + @Test + public void unsubscribeCluster() throws Exception { + String serviceName = randomDomainName(); + + EventListener listener = new EventListener() { + @Override + public void onEvent(Event event) { + System.out.println(((NamingEvent)event).getServiceName()); + System.out.println(((NamingEvent)event).getInstances()); + instances = ((NamingEvent)event).getInstances(); + } + }; + + naming.subscribe(serviceName, Arrays.asList("c1"), listener); + + naming.registerInstance(serviceName, "127.0.0.1", TEST_PORT, "c1"); + + while (instances.isEmpty()) { + Thread.sleep(1000L); + } + + Assert.assertTrue(verifyInstanceList(instances, naming.getAllInstances(serviceName))); + + naming.unsubscribe(serviceName, Arrays.asList("c1"), listener); + + instances = Collections.emptyList(); + naming.registerInstance(serviceName, "127.0.0.2", TEST_PORT, "c1"); + + int i = 0; + while (instances.isEmpty()) { + Thread.sleep(1000L); + if (i++ > 20) { + return; + } + } + + Assert.assertTrue(false); + } + +} diff --git a/test/src/test/java/com/alibaba/nacos/test/smoke/nacosSmoke_ITCase.java b/test/src/test/java/com/alibaba/nacos/test/smoke/nacosSmoke_ITCase.java new file mode 100644 index 00000000000..feec9ca05e4 --- /dev/null +++ b/test/src/test/java/com/alibaba/nacos/test/smoke/nacosSmoke_ITCase.java @@ -0,0 +1,58 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed 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. + */ +/* + * 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 com.alibaba.nacos.test.smoke; + +import org.apache.log4j.Logger; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class nacosSmoke_ITCase { + + private static Logger logger = Logger.getLogger(nacosSmoke_ITCase.class); + + @Before + public void setUp() { + logger.info(String.format("nacosSmoke_ITCase: %s;", "setUp")); + } + + @After + public void tearDown() { + logger.info(String.format("nacosSmoke_ITCase: %s;", "tearDown")); + } + + @Test + public void testSmoke() { + logger.info("nacosSmoke_ITCase :testSmoke"); + } +} diff --git a/test/src/test/resources/application.properties b/test/src/test/resources/application.properties new file mode 100644 index 00000000000..9097e310d9a --- /dev/null +++ b/test/src/test/resources/application.properties @@ -0,0 +1,6 @@ +# spring +management.security.enabled=false +server.servlet.context-path=/nacos +server.port=8080 + +nacos.standalone=true \ No newline at end of file diff --git a/test/src/test/resources/logback-test.xml b/test/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..f70c7d1e32a --- /dev/null +++ b/test/src/test/resources/logback-test.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + diff --git a/test/src/test/resources/schema.sql b/test/src/test/resources/schema.sql new file mode 100644 index 00000000000..ae4ae291bcd --- /dev/null +++ b/test/src/test/resources/schema.sql @@ -0,0 +1,161 @@ +CREATE SCHEMA diamond AUTHORIZATION diamond; + +CREATE TABLE config_info ( + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + app_name varchar(128), + content LONG VARCHAR NOT NULL, + md5 varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + src_user varchar(128) DEFAULT NULL, + src_ip varchar(20) DEFAULT NULL, + c_desc varchar(256) DEFAULT NULL, + c_use varchar(64) DEFAULT NULL, + effect varchar(64) DEFAULT NULL, + type varchar(64) DEFAULT NULL, + c_schema LONG VARCHAR DEFAULT NULL, + constraint configinfo_id_key PRIMARY KEY (id), + constraint uk_configinfo_datagrouptenant UNIQUE (data_id,group_id,tenant_id)); + +CREATE INDEX configinfo_dataid_key_idx ON config_info(data_id); +CREATE INDEX configinfo_groupid_key_idx ON config_info(group_id); +CREATE INDEX configinfo_dataid_group_key_idx ON config_info(data_id, group_id); + +CREATE TABLE his_config_info ( + id bigint NOT NULL, + nid bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + app_name varchar(128), + content LONG VARCHAR NOT NULL, + md5 varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00.000', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00.000', + src_user varchar(128), + src_ip varchar(20) DEFAULT NULL, + op_type char(10) DEFAULT NULL, + constraint hisconfiginfo_nid_key PRIMARY KEY (nid)); + +CREATE INDEX hisconfiginfo_dataid_key_idx ON his_config_info(data_id); +CREATE INDEX hisconfiginfo_gmt_create_idx ON his_config_info(gmt_create); +CREATE INDEX hisconfiginfo_gmt_modified_idx ON his_config_info(gmt_modified); + + +CREATE TABLE config_info_beta ( + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + app_name varchar(128), + content LONG VARCHAR NOT NULL, + beta_ips varchar(1024), + md5 varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + src_user varchar(128), + src_ip varchar(20) DEFAULT NULL, + constraint configinfobeta_id_key PRIMARY KEY (id), + constraint uk_configinfobeta_datagrouptenant UNIQUE (data_id,group_id,tenant_id)); + +CREATE TABLE config_info_tag ( + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + tag_id varchar(128) NOT NULL, + app_name varchar(128), + content LONG VARCHAR NOT NULL, + md5 varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + src_user varchar(128), + src_ip varchar(20) DEFAULT NULL, + constraint configinfotag_id_key PRIMARY KEY (id), + constraint uk_configinfotag_datagrouptenanttag UNIQUE (data_id,group_id,tenant_id,tag_id)); + +CREATE TABLE config_info_aggr ( + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + datum_id varchar(255) NOT NULL, + app_name varchar(128), + content LONG VARCHAR NOT NULL, + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + constraint configinfoaggr_id_key PRIMARY KEY (id), + constraint uk_configinfoaggr_datagrouptenantdatum UNIQUE (data_id,group_id,tenant_id,datum_id)); + +CREATE TABLE app_list ( + id bigint NOT NULL generated by default as identity, + app_name varchar(128) NOT NULL, + is_dynamic_collect_disabled smallint DEFAULT 0, + last_sub_info_collected_time timestamp DEFAULT '1970-01-01 08:00:00.0', + sub_info_lock_owner varchar(128), + sub_info_lock_time timestamp DEFAULT '1970-01-01 08:00:00.0', + constraint applist_id_key PRIMARY KEY (id), + constraint uk_appname UNIQUE (app_name)); + +CREATE TABLE app_configdata_relation_subs ( + id bigint NOT NULL generated by default as identity, + app_name varchar(128) NOT NULL, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', + constraint configdatarelationsubs_id_key PRIMARY KEY (id), + constraint uk_app_sub_config_datagroup UNIQUE (app_name, data_id, group_id)); + + +CREATE TABLE app_configdata_relation_pubs ( + id bigint NOT NULL generated by default as identity, + app_name varchar(128) NOT NULL, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', + constraint configdatarelationpubs_id_key PRIMARY KEY (id), + constraint uk_app_pub_config_datagroup UNIQUE (app_name, data_id, group_id)); + +CREATE TABLE config_tags_relation ( + id bigint NOT NULL, + tag_name varchar(128) NOT NULL, + tag_type varchar(64) DEFAULT NULL, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) DEFAULT '', + nid bigint NOT NULL generated by default as identity, + constraint config_tags_id_key PRIMARY KEY (nid), + constraint uk_configtagrelation_configidtag UNIQUE (id, tag_name, tag_type)); + +CREATE INDEX config_tags_tenant_id_idx ON config_tags_relation(tenant_id); + +CREATE TABLE group_capacity ( + id bigint NOT NULL generated by default as identity, + group_id varchar(128) DEFAULT '', + quota int DEFAULT 0, + usage int DEFAULT 0, + max_size int DEFAULT 0, + max_aggr_count int DEFAULT 0, + max_aggr_size int DEFAULT 0, + max_history_count int DEFAULT 0, + gmt_create timestamp DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', + constraint group_capacity_id_key PRIMARY KEY (id), + constraint uk_group_id UNIQUE (group_id)); + +CREATE TABLE tenant_capacity ( + id bigint NOT NULL generated by default as identity, + tenant_id varchar(128) DEFAULT '', + quota int DEFAULT 0, + usage int DEFAULT 0, + max_size int DEFAULT 0, + max_aggr_count int DEFAULT 0, + max_aggr_size int DEFAULT 0, + max_history_count int DEFAULT 0, + gmt_create timestamp DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', + constraint tenant_capacity_id_key PRIMARY KEY (id), + constraint uk_tenant_id UNIQUE (tenant_id)); +