From 91d20f34fdec6b77b0b27c874983ca57a2feb584 Mon Sep 17 00:00:00 2001 From: xingxuechao Date: Sat, 21 Jul 2018 00:27:23 +0800 Subject: [PATCH] nacos is coming --- .gitignore | 10 + .travis.yml | 31 + BUILDING | 38 + CONTRIBUTING.md | 31 + LICENSE | 201 ++ NOTICE | 32 + README.md | 49 + client/pom.xml | 107 + .../com/alibaba/nacos/api/NacosFactory.java | 86 + .../alibaba/nacos/api/PropertyKeyConst.java | 34 + .../nacos/api/config/ConfigFactory.java | 60 + .../nacos/api/config/ConfigService.java | 103 + .../api/config/filter/IConfigContext.java | 42 + .../api/config/filter/IConfigFilter.java | 69 + .../api/config/filter/IConfigFilterChain.java | 39 + .../api/config/filter/IConfigRequest.java | 41 + .../api/config/filter/IConfigResponse.java | 41 + .../api/config/filter/IFilterConfig.java | 39 + .../api/config/listener/AbstractListener.java | 36 + .../listener/AbstractSharedListener.java | 57 + .../nacos/api/config/listener/Listener.java | 43 + .../nacos/api/exception/NacosException.java | 110 + .../nacos/api/naming/NamingFactory.java | 34 + .../nacos/api/naming/NamingService.java | 176 + .../nacos/api/naming/listener/Event.java | 23 + .../api/naming/listener/EventListener.java | 29 + .../api/naming/listener/NamingEvent.java | 51 + .../naming/pojo/AbstractHealthChecker.java | 179 + .../nacos/api/naming/pojo/Cluster.java | 124 + .../nacos/api/naming/pojo/Instance.java | 166 + .../nacos/api/naming/pojo/Service.java | 108 + .../client/config/NacosConfigService.java | 267 ++ .../nacos/client/config/common/Constants.java | 127 + .../nacos/client/config/common/GroupKey.java | 123 + .../config/filter/impl/ConfigContext.java | 43 + .../filter/impl/ConfigFilterChainManager.java | 87 + .../config/filter/impl/ConfigRequest.java | 78 + .../config/filter/impl/ConfigResponse.java | 78 + .../nacos/client/config/impl/CacheData.java | 319 ++ .../client/config/impl/ClientWorker.java | 541 +++ .../client/config/impl/EventDispatcher.java | 133 + .../client/config/impl/HttpSimpleClient.java | 276 ++ .../nacos/client/config/impl/Limiter.java | 79 + .../config/impl/LocalConfigInfoProcessor.java | 197 ++ .../client/config/impl/ServerHttpAgent.java | 392 +++ .../client/config/impl/ServerListManager.java | 432 +++ .../nacos/client/config/impl/SpasAdapter.java | 111 + .../client/config/impl/TimerService.java | 48 + .../listener/impl/PropertiesListener.java | 61 + .../config/utils/ConcurrentDiskUtil.java | 247 ++ .../client/config/utils/ContentUtils.java | 78 + .../nacos/client/config/utils/IOUtils.java | 151 + .../nacos/client/config/utils/JVMUtil.java | 49 + .../nacos/client/config/utils/LogUtils.java | 59 + .../nacos/client/config/utils/MD5.java | 145 + .../nacos/client/config/utils/ParamUtils.java | 154 + .../client/config/utils/SnapShotSwitch.java | 42 + .../nacos/client/config/utils/TenantUtil.java | 44 + .../alibaba/nacos/client/identify/Base64.java | 707 ++++ .../nacos/client/identify/Constants.java | 47 + .../client/identify/CredentialListener.java | 29 + .../client/identify/CredentialService.java | 135 + .../client/identify/CredentialWatcher.java | 214 ++ .../nacos/client/identify/Credentials.java | 65 + .../nacos/client/identify/STSConfig.java | 128 + .../nacos/client/identify/SpasCredential.java | 38 + .../client/identify/SpasCredentialLoader.java | 31 + .../alibaba/nacos/client/logger/Level.java | 48 + .../alibaba/nacos/client/logger/Logger.java | 238 ++ .../nacos/client/logger/LoggerFactory.java | 100 + .../nacos/client/logger/json/JSONArray.java | 398 +++ .../nacos/client/logger/json/JSONAware.java | 30 + .../nacos/client/logger/json/JSONObject.java | 152 + .../client/logger/json/JSONStreamAware.java | 38 + .../nacos/client/logger/json/JSONValue.java | 343 ++ .../logger/json/parser/ContainerFactory.java | 40 + .../logger/json/parser/ContentHandler.java | 129 + .../client/logger/json/parser/JSONParser.java | 574 +++ .../logger/json/parser/ParseException.java | 105 + .../client/logger/json/parser/Yylex.java | 665 ++++ .../client/logger/json/parser/Yytoken.java | 81 + .../client/logger/log4j/Log4jLogger.java | 146 + .../logger/log4j/Log4jLoggerFactory.java | 53 + .../client/logger/log4j2/Log4j2Logger.java | 144 + .../logger/log4j2/Log4j2LoggerFactory.java | 55 + .../nacos/client/logger/nop/NopLogger.java | 99 + .../client/logger/nop/NopLoggerFactory.java | 36 + .../logger/option/AbstractActiveOption.java | 73 + .../client/logger/option/ActivateOption.java | 195 ++ .../logger/option/Log4j2ActivateOption.java | 274 ++ .../logger/option/Log4jActivateOption.java | 212 ++ .../option/Logback918ActivateOption.java | 334 ++ .../logger/option/LogbackActivateOption.java | 154 + .../option/LogbackLoggerContextUtil.java | 47 + .../Slf4jLog4j2AdapterActivateOption.java | 68 + .../Slf4jLog4jAdapterActivateOption.java | 71 + .../client/logger/slf4j/Slf4jLogger.java | 178 + .../logger/slf4j/Slf4jLoggerFactory.java | 51 + .../client/logger/support/AppenderInfo.java | 48 + .../support/DepthThrowableRenderer.java | 90 + .../nacos/client/logger/support/ErrorLog.java | 35 + .../client/logger/support/ILoggerFactory.java | 44 + .../client/logger/support/Log4jHelper.java | 201 ++ .../nacos/client/logger/support/LogLog.java | 143 + .../client/logger/support/LogbackHelper.java | 226 ++ .../client/logger/support/LoggerHelper.java | 252 ++ .../client/logger/support/LoggerInfo.java | 54 + .../client/logger/support/LoggerSupport.java | 198 ++ .../client/logger/util/FormattingTuple.java | 68 + .../client/logger/util/MessageFormatter.java | 421 +++ .../nacos/client/logger/util/MessageUtil.java | 47 + .../client/naming/NacosNamingService.java | 242 ++ .../naming/backups/FailoverReactor.java | 242 ++ .../nacos/client/naming/beat/BeatInfo.java | 66 + .../nacos/client/naming/beat/BeatReactor.java | 108 + .../naming/cache/ConcurrentDiskUtil.java | 238 ++ .../nacos/client/naming/cache/DiskCache.java | 151 + .../nacos/client/naming/core/Balancer.java | 98 + .../nacos/client/naming/core/Domain.java | 245 ++ .../client/naming/core/EventDispatcher.java | 136 + .../nacos/client/naming/core/HostReactor.java | 432 +++ .../nacos/client/naming/core/ProtectMode.java | 36 + .../nacos/client/naming/core/PushRecver.java | 119 + .../nacos/client/naming/net/HttpClient.java | 199 ++ .../nacos/client/naming/net/NamingProxy.java | 348 ++ .../nacos/client/naming/utils/Chooser.java | 229 ++ .../client/naming/utils/CollectionUtils.java | 171 + .../client/naming/utils/GenericPoller.java | 41 + .../nacos/client/naming/utils/IoUtils.java | 188 + .../nacos/client/naming/utils/JvmRandom.java | 246 ++ .../nacos/client/naming/utils/LogUtils.java | 54 + .../nacos/client/naming/utils/NetUtils.java | 40 + .../nacos/client/naming/utils/Pair.java | 38 + .../nacos/client/naming/utils/Poller.java | 38 + .../client/naming/utils/RandomUtils.java | 194 ++ .../client/naming/utils/StringUtils.java | 164 + .../naming/utils/ThreadLocalRandom.java | 288 ++ .../client/naming/utils/UtilAndComs.java | 44 + .../nacos/client/utils/AppNameUtils.java | 96 + .../alibaba/nacos/client/utils/EnvUtil.java | 115 + .../alibaba/nacos/client/utils/IPUtil.java | 55 + .../alibaba/nacos/client/utils/JSONUtils.java | 60 + .../alibaba/nacos/client/utils/ParamUtil.java | 162 + .../nacos/client/utils/StringUtils.java | 69 + .../src/main/resources/application.properties | 1 + .../com/alibaba/nacos/client/AppTest.java | 53 + common/license | 15 + common/pom.xml | 38 + .../alibaba/nacos/common/AppendLicense.java | 85 + .../alibaba/nacos/common/util/IoUtils.java | 173 + .../alibaba/nacos/common/util/Md5Utils.java | 58 + .../com/alibaba/nacos/common/util/Pair.java | 51 + .../alibaba/nacos/common/util/SystemUtil.java | 63 + .../alibaba/nacos/common/util/UuidUtil.java | 13 + .../com/alibaba/nacos/common/AppTest.java | 53 + config/pom.xml | 155 + config/src/main/java/LogbackInitTest.java | 68 + .../alibaba/nacos/config/server/Config.java | 40 + .../aspect/CapacityManagementAspect.java | 441 +++ .../server/aspect/RequestLogAspect.java | 104 + .../config/server/constant/Constants.java | 216 ++ .../config/server/constant/CounterMode.java | 40 + .../server/controller/CapacityController.java | 160 + .../controller/CommunicationController.java | 112 + .../server/controller/ConfigController.java | 368 ++ .../server/controller/ConfigServletInner.java | 343 ++ .../server/controller/HealthController.java | 79 + .../server/controller/HistoryController.java | 61 + .../server/controller/ListenerController.java | 96 + .../server/controller/OpsController.java | 60 + .../exception/GlobalExceptionHandler.java | 67 + .../server/exception/NacosException.java | 94 + .../nacos/config/server/filter/WebFilter.java | 84 + .../config/server/manager/AbstractTask.java | 64 + .../config/server/manager/TaskManager.java | 298 ++ .../server/manager/TaskManagerMBean.java | 34 + .../config/server/manager/TaskProcessor.java | 35 + .../nacos/config/server/model/ACLInfo.java | 47 + .../nacos/config/server/model/AuthType.java | 29 + .../nacos/config/server/model/CacheItem.java | 109 + .../server/model/ConfigAdvanceInfo.java | 100 + .../server/model/ConfigHistoryInfo.java | 163 + .../nacos/config/server/model/ConfigInfo.java | 83 + .../config/server/model/ConfigInfo4Beta.java | 58 + .../config/server/model/ConfigInfo4Tag.java | 58 + .../config/server/model/ConfigInfoAggr.java | 196 ++ .../config/server/model/ConfigInfoBase.java | 215 ++ .../config/server/model/ConfigInfoBaseEx.java | 84 + .../server/model/ConfigInfoChanged.java | 119 + .../config/server/model/ConfigInfoEx.java | 89 + .../server/model/ConfigInfoWrapper.java | 47 + .../nacos/config/server/model/ConfigKey.java | 70 + .../nacos/config/server/model/GroupInfo.java | 135 + .../server/model/GroupkeyListenserStatus.java | 54 + .../config/server/model/HistoryContext.java | 121 + .../nacos/config/server/model/Page.java | 88 + .../config/server/model/RestPageResult.java | 83 + .../nacos/config/server/model/RestResult.java | 83 + .../config/server/model/SampleResult.java | 43 + .../nacos/config/server/model/SubInfo.java | 63 + .../config/server/model/SubscriberStatus.java | 79 + .../server/model/app/ApplicationInfo.java | 112 + .../model/app/ApplicationPublishRecord.java | 49 + .../config/server/model/app/GroupKey.java | 69 + .../config/server/model/app/MonitorInfo.java | 133 + .../server/model/capacity/Capacity.java | 113 + .../server/model/capacity/GroupCapacity.java | 33 + .../server/model/capacity/TenantCapacity.java | 33 + .../config/server/monitor/MemoryMonitor.java | 92 + .../server/monitor/ResponseMonitor.java | 97 + .../config/server/service/AggrWhitelist.java | 97 + .../service/BasicDataSourceServiceImpl.java | 331 ++ .../server/service/ClientIpWhiteList.java | 88 + .../server/service/ClientTrackService.java | 184 + .../server/service/ConfigDataChangeEvent.java | 69 + .../config/server/service/ConfigService.java | 587 ++++ .../server/service/ConfigSubService.java | 265 ++ .../server/service/DataSourceService.java | 72 + .../nacos/config/server/service/DiskUtil.java | 260 ++ .../server/service/DynamicDataSource.java | 52 + .../server/service/LocalDataChangeEvent.java | 53 + .../service/LocalDataSourceServiceImpl.java | 205 ++ .../server/service/LongPullingService.java | 513 +++ .../config/server/service/PersistService.java | 3090 +++++++++++++++++ .../server/service/ServerListService.java | 477 +++ .../config/server/service/SwitchService.java | 126 + .../server/service/TimerTaskService.java | 49 + .../service/capacity/CapacityService.java | 499 +++ .../capacity/GroupCapacityPersistService.java | 327 ++ .../TenantCapacityPersistService.java | 277 ++ .../server/service/dump/DumpService.java | 403 +++ .../config/server/service/dump/DumpTask.java | 444 +++ .../server/service/merge/MergeDataTask.java | 68 + .../service/merge/MergeDatumService.java | 155 + .../service/merge/MergeTaskProcessor.java | 134 + .../service/notify/AsyncNotifyService.java | 366 ++ .../server/service/notify/NotifyService.java | 111 + .../service/notify/NotifySingleService.java | 161 + .../server/service/notify/NotifyTask.java | 94 + .../service/notify/NotifyTaskProcessor.java | 102 + .../service/trace/ConfigTraceService.java | 109 + .../server/utils/AccumulateStatCount.java | 41 + .../config/server/utils/AppNameUtils.java | 97 + .../config/server/utils/ContentUtils.java | 75 + .../nacos/config/server/utils/GroupKey.java | 120 + .../nacos/config/server/utils/GroupKey2.java | 109 + .../nacos/config/server/utils/IPUtil.java | 55 + .../nacos/config/server/utils/JSONUtils.java | 60 + .../nacos/config/server/utils/LogUtil.java | 100 + .../nacos/config/server/utils/MD5.java | 168 + .../nacos/config/server/utils/MD5Util.java | 183 + .../config/server/utils/PaginationHelper.java | 215 ++ .../nacos/config/server/utils/ParamUtils.java | 155 + .../config/server/utils/PropertyUtil.java | 276 ++ .../nacos/config/server/utils/Protocol.java | 48 + .../config/server/utils/RegexParser.java | 82 + .../config/server/utils/RequestUtil.java | 38 + .../config/server/utils/ResourceUtils.java | 214 ++ .../config/server/utils/ResponseUtil.java | 40 + .../server/utils/RunningConfigUtils.java | 68 + .../config/server/utils/SimpleCache.java | 58 + .../config/server/utils/SimpleFlowData.java | 124 + .../config/server/utils/SimpleIPFlowData.java | 113 + .../server/utils/SimpleReadWriteLock.java | 63 + .../server/utils/SingletonRepository.java | 61 + .../config/server/utils/StatConstants.java | 37 + .../config/server/utils/StringUtils.java | 69 + .../config/server/utils/SystemConfig.java | 65 + .../nacos/config/server/utils/ThreadUtil.java | 39 + .../nacos/config/server/utils/TimeUtils.java | 45 + .../config/server/utils/TimeoutUtils.java | 104 + .../config/server/utils/TraceLogUtil.java | 38 + .../config/server/utils/UrlAnalysisUtils.java | 75 + .../server/utils/event/EventDispatcher.java | 148 + .../src/main/resources/application.properties | 17 + config/src/main/resources/banner.txt | 15 + .../main/resources/diamond-app-collector.sql | 31 + config/src/main/resources/diamond-db.sql | 133 + .../main/resources/nacos-config-logback.xml | 283 ++ config/src/main/resources/schema.sql | 161 + config/src/main/resources/version/version.txt | 1 + .../nacos/config/mock/FilterConfigMock.java | 56 + .../nacos/config/mock/ServletContextMock.java | 356 ++ .../controller/HealthControllerUnitTest.java | 67 + .../server/service/AggrWhitelistTest.java | 57 + .../service/ClientTrackServiceTest.java | 62 + .../server/service/DiskServiceUnitTest.java | 66 + .../config/server/utils/GroupKeyTest.java | 68 + .../server/utils/SimpleReadWriteLockTest.java | 77 + .../utils/event/EventDispatcherTest.java | 99 + config/src/test/resources/application.xml-bk | 0 .../test/resources/application.xml-test4derby | 0 config/src/test/resources/jdbc.properties | 0 config/src/test/resources/log4j.properties | 20 + config/src/test/resources/user.properties | 1 + console/pom.xml | 71 + .../main/java/com/alibaba/nacos/Nacos.java | 34 + .../src/main/resources/application.properties | 47 + console/src/main/resources/banner.txt | 15 + .../main/resources/diamond-server-logback.xml | 261 ++ console/src/main/resources/schema.sql | 161 + core/pom.xml | 35 + .../main/java/com/alibaba/nacos/core/App.java | 28 + .../java/com/alibaba/nacos/core/AppTest.java | 53 + distribution/LICENSE-BIN | 334 ++ distribution/NOTICE-BIN | 36 + distribution/bin/deploy.sh | 4 + distribution/bin/deploy_naming.sh | 5 + distribution/bin/run_naming.sh | 16 + distribution/bin/shutdown.cmd | 12 + distribution/bin/shutdown.sh | 13 + distribution/bin/startup.cmd | 28 + distribution/bin/startup.sh | 57 + distribution/conf/application.properties | 12 + .../conf/application.properties.example | 12 + distribution/conf/cluster.conf.example | 5 + distribution/conf/nacos-logback.xml | 505 +++ distribution/conf/schema.sql | 161 + distribution/pom.xml | 185 + distribution/release-client.xml | 71 + distribution/release-config.xml | 71 + distribution/release-core.xml | 70 + distribution/release-nacos.xml | 50 + distribution/release-naming.xml | 71 + doc/Nacos_Logo.png | Bin 0 -> 35417 bytes doc/arch.png | 0 example/pom.xml | 42 + .../java/com/alibaba/nacos/example/App.java | 40 + .../alibaba/nacos/example/ConfigExample.java | 71 + .../alibaba/nacos/example/NamingExample.java | 58 + .../com/alibaba/nacos/example/AppTest.java | 53 + naming/default/failover/nacos.test.4 | 1 + naming/pom.xml | 186 + .../com/alibaba/nacos/naming/NamingApp.java | 35 + .../alibaba/nacos/naming/acl/AuthChecker.java | 118 + .../alibaba/nacos/naming/acl/AuthInfo.java | 80 + .../nacos/naming/boot/RunningConfig.java | 66 + .../naming/controllers/CatalogController.java | 22 + .../naming/controllers/ClusterController.java | 22 + .../naming/controllers/CmdbController.java | 22 + .../naming/controllers/HealthController.java | 22 + .../controllers/InstanceController.java | 172 + .../controllers/OperatorController.java | 22 + .../naming/controllers/ServiceController.java | 28 + .../alibaba/nacos/naming/core/Cluster.java | 461 +++ .../nacos/naming/core/DistroMapper.java | 496 +++ .../com/alibaba/nacos/naming/core/Domain.java | 132 + .../nacos/naming/core/DomainsManager.java | 718 ++++ .../alibaba/nacos/naming/core/IpAddress.java | 399 +++ .../naming/core/VirtualClusterDomain.java | 618 ++++ .../naming/exception/NacosException.java | 80 + .../exception/ResponseExceptionHandler.java | 30 + .../AbstractHealthCheckConfig.java | 327 ++ .../AbstractHealthCheckProcessor.java | 328 ++ .../healthcheck/ClientBeatCheckTask.java | 96 + .../healthcheck/ClientBeatProcessor.java | 95 + .../naming/healthcheck/HealthCheckMode.java | 21 + .../healthcheck/HealthCheckReactor.java | 48 + .../naming/healthcheck/HealthCheckStatus.java | 81 + .../naming/healthcheck/HealthCheckTask.java | 159 + .../naming/healthcheck/HealthCheckType.java | 34 + .../healthcheck/HttpHealthCheckProcessor.java | 200 ++ .../MysqlHealthCheckProcessor.java | 210 ++ .../nacos/naming/healthcheck/RsInfo.java | 120 + .../healthcheck/TcpSuperSenseProcessor.java | 420 +++ .../naming/misc/DomainStatusSynchronizer.java | 98 + .../alibaba/nacos/naming/misc/HttpClient.java | 398 +++ .../alibaba/nacos/naming/misc/Loggers.java | 68 + .../alibaba/nacos/naming/misc/Message.java | 31 + .../nacos/naming/misc/NamingProxy.java | 264 ++ .../alibaba/nacos/naming/misc/NetUtils.java | 48 + .../naming/misc/ServerStatusSynchronizer.java | 71 + .../com/alibaba/nacos/naming/misc/Switch.java | 349 ++ .../nacos/naming/misc/SwitchDomain.java | 391 +++ .../nacos/naming/misc/SwitchEntry.java | 62 + .../nacos/naming/misc/Synchronizer.java | 38 + .../nacos/naming/misc/UtilsAndCommons.java | 204 ++ .../monitor/PerformanceLoggerThread.java | 142 + .../alibaba/nacos/naming/push/ClientInfo.java | 145 + .../alibaba/nacos/naming/push/DataSource.java | 30 + .../nacos/naming/push/PushService.java | 681 ++++ .../com/alibaba/nacos/naming/raft/Datum.java | 27 + .../nacos/naming/raft/GlobalExecutor.java | 58 + .../alibaba/nacos/naming/raft/PeerSet.java | 221 ++ .../alibaba/nacos/naming/raft/RaftCore.java | 1015 ++++++ .../nacos/naming/raft/RaftListener.java | 56 + .../alibaba/nacos/naming/raft/RaftPeer.java | 87 + .../alibaba/nacos/naming/raft/RaftProxy.java | 72 + .../alibaba/nacos/naming/raft/RaftStore.java | 193 + .../alibaba/nacos/naming/web/ApiCommands.java | 2452 +++++++++++++ .../alibaba/nacos/naming/web/AuthFilter.java | 110 + .../alibaba/nacos/naming/web/BaseServlet.java | 71 + .../nacos/naming/web/DistroFilter.java | 111 + .../nacos/naming/web/MockHttpRequest.java | 390 +++ .../nacos/naming/web/NamingConfig.java | 63 + .../alibaba/nacos/naming/web/NeedAuth.java | 25 + .../nacos/naming/web/RaftCommands.java | 246 ++ .../src/main/resources/application.properties | 10 + naming/src/main/resources/banner.txt | 15 + naming/src/main/resources/naming-logback.xml | 247 ++ .../com/alibaba/nacos/naming/BaseTest.java | 50 + .../controllers/InstanceControllerTest.java | 154 + .../nacos/naming/core/ClusterTest.java | 100 + .../alibaba/nacos/naming/core/DomainTest.java | 96 + .../nacos/naming/core/DomainsManagerTest.java | 80 + .../nacos/naming/core/IpAddressTest.java | 53 + .../alibaba/nacos/naming/misc/SwitchTest.java | 59 + .../nacos/naming/raft/RaftStoreTest.java | 42 + .../nacos/naming/web/APICommandsTest.java | 130 + pom.xml | 685 ++++ style/codeStyle.md | 31 + test/it_test.log | 3 + test/pom.xml | 77 + .../main/java/com/alibaba/nacos/test/App.java | 28 + .../java/com/alibaba/nacos/test/AppTest.java | 53 + .../nacos/test/config/ConfigAPI_ITCase.java | 564 +++ .../naming/DeregisterInstance_ITCase.java | 115 + .../alibaba/nacos/test/naming/NamingBase.java | 174 + .../com/alibaba/nacos/test/naming/Params.java | 42 + .../nacos/test/naming/RandomUtils.java | 348 ++ .../test/naming/RegisterInstance_ITCase.java | 153 + .../nacos/test/naming/RestAPI_ITCase.java | 683 ++++ .../test/naming/SelectInstances_ITCase.java | 153 + .../SelectOneHealthyInstance_ITCase.java | 168 + .../nacos/test/naming/Starter_ITCase.java | 22 + .../test/naming/SubscribeCluster_ITCase.java | 222 ++ .../nacos/test/naming/Subscribe_ITCase.java | 193 + .../nacos/test/naming/Unsubscribe_ITCase.java | 152 + .../nacos/test/smoke/nacosSmoke_ITCase.java | 58 + .../src/test/resources/application.properties | 6 + test/src/test/resources/logback-test.xml | 33 + test/src/test/resources/schema.sql | 161 + 432 files changed, 62877 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 BUILDING create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 NOTICE create mode 100644 README.md create mode 100644 client/pom.xml create mode 100644 client/src/main/java/com/alibaba/nacos/api/NacosFactory.java create mode 100644 client/src/main/java/com/alibaba/nacos/api/PropertyKeyConst.java create mode 100644 client/src/main/java/com/alibaba/nacos/api/config/ConfigFactory.java create mode 100644 client/src/main/java/com/alibaba/nacos/api/config/ConfigService.java create mode 100644 client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigContext.java create mode 100644 client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigFilter.java create mode 100644 client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigFilterChain.java create mode 100644 client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigRequest.java create mode 100644 client/src/main/java/com/alibaba/nacos/api/config/filter/IConfigResponse.java create mode 100644 client/src/main/java/com/alibaba/nacos/api/config/filter/IFilterConfig.java create mode 100644 client/src/main/java/com/alibaba/nacos/api/config/listener/AbstractListener.java create mode 100644 client/src/main/java/com/alibaba/nacos/api/config/listener/AbstractSharedListener.java create mode 100644 client/src/main/java/com/alibaba/nacos/api/config/listener/Listener.java create mode 100644 client/src/main/java/com/alibaba/nacos/api/exception/NacosException.java create mode 100644 client/src/main/java/com/alibaba/nacos/api/naming/NamingFactory.java create mode 100644 client/src/main/java/com/alibaba/nacos/api/naming/NamingService.java create mode 100644 client/src/main/java/com/alibaba/nacos/api/naming/listener/Event.java create mode 100644 client/src/main/java/com/alibaba/nacos/api/naming/listener/EventListener.java create mode 100644 client/src/main/java/com/alibaba/nacos/api/naming/listener/NamingEvent.java create mode 100644 client/src/main/java/com/alibaba/nacos/api/naming/pojo/AbstractHealthChecker.java create mode 100644 client/src/main/java/com/alibaba/nacos/api/naming/pojo/Cluster.java create mode 100644 client/src/main/java/com/alibaba/nacos/api/naming/pojo/Instance.java create mode 100644 client/src/main/java/com/alibaba/nacos/api/naming/pojo/Service.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/config/NacosConfigService.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/config/common/Constants.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/config/common/GroupKey.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/config/filter/impl/ConfigContext.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/config/filter/impl/ConfigFilterChainManager.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/config/filter/impl/ConfigRequest.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/config/filter/impl/ConfigResponse.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/config/impl/CacheData.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/config/impl/ClientWorker.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/config/impl/EventDispatcher.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/config/impl/HttpSimpleClient.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/config/impl/Limiter.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/config/impl/LocalConfigInfoProcessor.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/config/impl/ServerHttpAgent.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/config/impl/ServerListManager.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/config/impl/SpasAdapter.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/config/impl/TimerService.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/config/listener/impl/PropertiesListener.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/config/utils/ConcurrentDiskUtil.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/config/utils/ContentUtils.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/config/utils/IOUtils.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/config/utils/JVMUtil.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/config/utils/LogUtils.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/config/utils/MD5.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/config/utils/ParamUtils.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/config/utils/SnapShotSwitch.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/config/utils/TenantUtil.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/identify/Base64.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/identify/Constants.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/identify/CredentialListener.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/identify/CredentialService.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/identify/CredentialWatcher.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/identify/Credentials.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/identify/STSConfig.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/identify/SpasCredential.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/identify/SpasCredentialLoader.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/Level.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/Logger.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/LoggerFactory.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/json/JSONArray.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/json/JSONAware.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/json/JSONObject.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/json/JSONStreamAware.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/json/JSONValue.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/json/parser/ContainerFactory.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/json/parser/ContentHandler.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/json/parser/JSONParser.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/json/parser/ParseException.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/json/parser/Yylex.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/json/parser/Yytoken.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/log4j/Log4jLogger.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/log4j/Log4jLoggerFactory.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/log4j2/Log4j2Logger.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/log4j2/Log4j2LoggerFactory.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/nop/NopLogger.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/nop/NopLoggerFactory.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/option/AbstractActiveOption.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/option/ActivateOption.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/option/Log4j2ActivateOption.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/option/Log4jActivateOption.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/option/Logback918ActivateOption.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/option/LogbackActivateOption.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/option/LogbackLoggerContextUtil.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/option/Slf4jLog4j2AdapterActivateOption.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/option/Slf4jLog4jAdapterActivateOption.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/slf4j/Slf4jLogger.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/slf4j/Slf4jLoggerFactory.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/support/AppenderInfo.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/support/DepthThrowableRenderer.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/support/ErrorLog.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/support/ILoggerFactory.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/support/Log4jHelper.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/support/LogLog.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/support/LogbackHelper.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/support/LoggerHelper.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/support/LoggerInfo.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/support/LoggerSupport.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/util/FormattingTuple.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/util/MessageFormatter.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/logger/util/MessageUtil.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/naming/backups/FailoverReactor.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/naming/beat/BeatInfo.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/naming/beat/BeatReactor.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/naming/cache/ConcurrentDiskUtil.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/naming/cache/DiskCache.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/naming/core/Balancer.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/naming/core/Domain.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/naming/core/EventDispatcher.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/naming/core/HostReactor.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/naming/core/ProtectMode.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/naming/core/PushRecver.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/naming/net/HttpClient.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/naming/net/NamingProxy.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/naming/utils/Chooser.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/naming/utils/CollectionUtils.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/naming/utils/GenericPoller.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/naming/utils/IoUtils.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/naming/utils/JvmRandom.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/naming/utils/LogUtils.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/naming/utils/NetUtils.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/naming/utils/Pair.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/naming/utils/Poller.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/naming/utils/RandomUtils.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/naming/utils/StringUtils.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/naming/utils/ThreadLocalRandom.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/naming/utils/UtilAndComs.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/utils/AppNameUtils.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/utils/EnvUtil.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/utils/IPUtil.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/utils/JSONUtils.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/utils/ParamUtil.java create mode 100644 client/src/main/java/com/alibaba/nacos/client/utils/StringUtils.java create mode 100644 client/src/main/resources/application.properties create mode 100644 client/src/test/java/com/alibaba/nacos/client/AppTest.java create mode 100644 common/license create mode 100644 common/pom.xml create mode 100644 common/src/main/java/com/alibaba/nacos/common/AppendLicense.java create mode 100644 common/src/main/java/com/alibaba/nacos/common/util/IoUtils.java create mode 100644 common/src/main/java/com/alibaba/nacos/common/util/Md5Utils.java create mode 100644 common/src/main/java/com/alibaba/nacos/common/util/Pair.java create mode 100644 common/src/main/java/com/alibaba/nacos/common/util/SystemUtil.java create mode 100644 common/src/main/java/com/alibaba/nacos/common/util/UuidUtil.java create mode 100644 common/src/test/java/com/alibaba/nacos/common/AppTest.java create mode 100644 config/pom.xml create mode 100644 config/src/main/java/LogbackInitTest.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/Config.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/aspect/CapacityManagementAspect.java create mode 100755 config/src/main/java/com/alibaba/nacos/config/server/aspect/RequestLogAspect.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/constant/Constants.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/constant/CounterMode.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/controller/CapacityController.java create mode 100755 config/src/main/java/com/alibaba/nacos/config/server/controller/CommunicationController.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigController.java create mode 100755 config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigServletInner.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/controller/HealthController.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/controller/HistoryController.java create mode 100755 config/src/main/java/com/alibaba/nacos/config/server/controller/ListenerController.java create mode 100755 config/src/main/java/com/alibaba/nacos/config/server/controller/OpsController.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/exception/GlobalExceptionHandler.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/exception/NacosException.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/filter/WebFilter.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/manager/AbstractTask.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/manager/TaskManager.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/manager/TaskManagerMBean.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/manager/TaskProcessor.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/ACLInfo.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/AuthType.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/CacheItem.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/ConfigAdvanceInfo.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/ConfigHistoryInfo.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfo.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfo4Beta.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfo4Tag.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoAggr.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoBase.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoBaseEx.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoChanged.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoEx.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoWrapper.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/ConfigKey.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/GroupInfo.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/GroupkeyListenserStatus.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/HistoryContext.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/Page.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/RestPageResult.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/RestResult.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/SampleResult.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/SubInfo.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/SubscriberStatus.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/app/ApplicationInfo.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/app/ApplicationPublishRecord.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/app/GroupKey.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/app/MonitorInfo.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/capacity/Capacity.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/capacity/GroupCapacity.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/model/capacity/TenantCapacity.java create mode 100755 config/src/main/java/com/alibaba/nacos/config/server/monitor/MemoryMonitor.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/monitor/ResponseMonitor.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/service/AggrWhitelist.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/service/BasicDataSourceServiceImpl.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/service/ClientIpWhiteList.java create mode 100755 config/src/main/java/com/alibaba/nacos/config/server/service/ClientTrackService.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/service/ConfigDataChangeEvent.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/service/ConfigService.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/service/ConfigSubService.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/service/DataSourceService.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/service/DiskUtil.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/service/DynamicDataSource.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/service/LocalDataChangeEvent.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/service/LocalDataSourceServiceImpl.java create mode 100755 config/src/main/java/com/alibaba/nacos/config/server/service/LongPullingService.java create mode 100755 config/src/main/java/com/alibaba/nacos/config/server/service/PersistService.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/service/ServerListService.java create mode 100755 config/src/main/java/com/alibaba/nacos/config/server/service/SwitchService.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/service/TimerTaskService.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/service/capacity/CapacityService.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/service/capacity/GroupCapacityPersistService.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/service/capacity/TenantCapacityPersistService.java create mode 100755 config/src/main/java/com/alibaba/nacos/config/server/service/dump/DumpService.java create mode 100755 config/src/main/java/com/alibaba/nacos/config/server/service/dump/DumpTask.java create mode 100755 config/src/main/java/com/alibaba/nacos/config/server/service/merge/MergeDataTask.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/service/merge/MergeDatumService.java create mode 100755 config/src/main/java/com/alibaba/nacos/config/server/service/merge/MergeTaskProcessor.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/service/notify/AsyncNotifyService.java create mode 100755 config/src/main/java/com/alibaba/nacos/config/server/service/notify/NotifyService.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/service/notify/NotifySingleService.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/service/notify/NotifyTask.java create mode 100755 config/src/main/java/com/alibaba/nacos/config/server/service/notify/NotifyTaskProcessor.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/service/trace/ConfigTraceService.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/AccumulateStatCount.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/AppNameUtils.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/ContentUtils.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/GroupKey.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/GroupKey2.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/IPUtil.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/JSONUtils.java create mode 100755 config/src/main/java/com/alibaba/nacos/config/server/utils/LogUtil.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/MD5.java create mode 100755 config/src/main/java/com/alibaba/nacos/config/server/utils/MD5Util.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/PaginationHelper.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/ParamUtils.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/PropertyUtil.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/Protocol.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/RegexParser.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/RequestUtil.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/ResourceUtils.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/ResponseUtil.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/RunningConfigUtils.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/SimpleCache.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/SimpleFlowData.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/SimpleIPFlowData.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/SimpleReadWriteLock.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/SingletonRepository.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/StatConstants.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/StringUtils.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/SystemConfig.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/ThreadUtil.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/TimeUtils.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/TimeoutUtils.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/TraceLogUtil.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/UrlAnalysisUtils.java create mode 100644 config/src/main/java/com/alibaba/nacos/config/server/utils/event/EventDispatcher.java create mode 100644 config/src/main/resources/application.properties create mode 100644 config/src/main/resources/banner.txt create mode 100644 config/src/main/resources/diamond-app-collector.sql create mode 100644 config/src/main/resources/diamond-db.sql create mode 100755 config/src/main/resources/nacos-config-logback.xml create mode 100644 config/src/main/resources/schema.sql create mode 100755 config/src/main/resources/version/version.txt create mode 100644 config/src/test/java/com/alibaba/nacos/config/mock/FilterConfigMock.java create mode 100644 config/src/test/java/com/alibaba/nacos/config/mock/ServletContextMock.java create mode 100644 config/src/test/java/com/alibaba/nacos/config/server/controller/HealthControllerUnitTest.java create mode 100644 config/src/test/java/com/alibaba/nacos/config/server/service/AggrWhitelistTest.java create mode 100644 config/src/test/java/com/alibaba/nacos/config/server/service/ClientTrackServiceTest.java create mode 100755 config/src/test/java/com/alibaba/nacos/config/server/service/DiskServiceUnitTest.java create mode 100644 config/src/test/java/com/alibaba/nacos/config/server/utils/GroupKeyTest.java create mode 100644 config/src/test/java/com/alibaba/nacos/config/server/utils/SimpleReadWriteLockTest.java create mode 100755 config/src/test/java/com/alibaba/nacos/config/server/utils/event/EventDispatcherTest.java create mode 100644 config/src/test/resources/application.xml-bk create mode 100644 config/src/test/resources/application.xml-test4derby create mode 100755 config/src/test/resources/jdbc.properties create mode 100644 config/src/test/resources/log4j.properties create mode 100755 config/src/test/resources/user.properties create mode 100644 console/pom.xml create mode 100644 console/src/main/java/com/alibaba/nacos/Nacos.java create mode 100644 console/src/main/resources/application.properties create mode 100644 console/src/main/resources/banner.txt create mode 100755 console/src/main/resources/diamond-server-logback.xml create mode 100644 console/src/main/resources/schema.sql create mode 100644 core/pom.xml create mode 100644 core/src/main/java/com/alibaba/nacos/core/App.java create mode 100644 core/src/test/java/com/alibaba/nacos/core/AppTest.java create mode 100644 distribution/LICENSE-BIN create mode 100644 distribution/NOTICE-BIN create mode 100644 distribution/bin/deploy.sh create mode 100644 distribution/bin/deploy_naming.sh create mode 100644 distribution/bin/run_naming.sh create mode 100755 distribution/bin/shutdown.cmd create mode 100644 distribution/bin/shutdown.sh create mode 100755 distribution/bin/startup.cmd create mode 100644 distribution/bin/startup.sh create mode 100644 distribution/conf/application.properties create mode 100644 distribution/conf/application.properties.example create mode 100644 distribution/conf/cluster.conf.example create mode 100644 distribution/conf/nacos-logback.xml create mode 100644 distribution/conf/schema.sql create mode 100644 distribution/pom.xml create mode 100644 distribution/release-client.xml create mode 100644 distribution/release-config.xml create mode 100644 distribution/release-core.xml create mode 100644 distribution/release-nacos.xml create mode 100644 distribution/release-naming.xml create mode 100644 doc/Nacos_Logo.png create mode 100644 doc/arch.png create mode 100644 example/pom.xml create mode 100644 example/src/main/java/com/alibaba/nacos/example/App.java create mode 100644 example/src/main/java/com/alibaba/nacos/example/ConfigExample.java create mode 100644 example/src/main/java/com/alibaba/nacos/example/NamingExample.java create mode 100644 example/src/test/java/com/alibaba/nacos/example/AppTest.java create mode 100644 naming/default/failover/nacos.test.4 create mode 100644 naming/pom.xml create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/NamingApp.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/acl/AuthChecker.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/acl/AuthInfo.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/boot/RunningConfig.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/controllers/CatalogController.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/controllers/ClusterController.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/controllers/CmdbController.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/controllers/HealthController.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/controllers/InstanceController.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/controllers/OperatorController.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/controllers/ServiceController.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/core/Cluster.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/core/DistroMapper.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/core/Domain.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/core/DomainsManager.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/core/IpAddress.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/core/VirtualClusterDomain.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/exception/NacosException.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/exception/ResponseExceptionHandler.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/healthcheck/AbstractHealthCheckConfig.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/healthcheck/AbstractHealthCheckProcessor.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/healthcheck/ClientBeatCheckTask.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/healthcheck/ClientBeatProcessor.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/healthcheck/HealthCheckMode.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/healthcheck/HealthCheckReactor.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/healthcheck/HealthCheckStatus.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/healthcheck/HealthCheckTask.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/healthcheck/HealthCheckType.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/healthcheck/HttpHealthCheckProcessor.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/healthcheck/MysqlHealthCheckProcessor.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/healthcheck/RsInfo.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/healthcheck/TcpSuperSenseProcessor.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/misc/DomainStatusSynchronizer.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/misc/HttpClient.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/misc/Loggers.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/misc/Message.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/misc/NamingProxy.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/misc/NetUtils.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/misc/ServerStatusSynchronizer.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/misc/Switch.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/misc/SwitchDomain.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/misc/SwitchEntry.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/misc/Synchronizer.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/misc/UtilsAndCommons.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/monitor/PerformanceLoggerThread.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/push/ClientInfo.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/push/DataSource.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/push/PushService.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/raft/Datum.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/raft/GlobalExecutor.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/raft/PeerSet.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/raft/RaftCore.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/raft/RaftListener.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/raft/RaftPeer.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/raft/RaftProxy.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/raft/RaftStore.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/web/ApiCommands.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/web/AuthFilter.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/web/BaseServlet.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/web/DistroFilter.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/web/MockHttpRequest.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/web/NamingConfig.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/web/NeedAuth.java create mode 100644 naming/src/main/java/com/alibaba/nacos/naming/web/RaftCommands.java create mode 100644 naming/src/main/resources/application.properties create mode 100644 naming/src/main/resources/banner.txt create mode 100644 naming/src/main/resources/naming-logback.xml create mode 100644 naming/src/test/java/com/alibaba/nacos/naming/BaseTest.java create mode 100644 naming/src/test/java/com/alibaba/nacos/naming/controllers/InstanceControllerTest.java create mode 100644 naming/src/test/java/com/alibaba/nacos/naming/core/ClusterTest.java create mode 100644 naming/src/test/java/com/alibaba/nacos/naming/core/DomainTest.java create mode 100644 naming/src/test/java/com/alibaba/nacos/naming/core/DomainsManagerTest.java create mode 100644 naming/src/test/java/com/alibaba/nacos/naming/core/IpAddressTest.java create mode 100644 naming/src/test/java/com/alibaba/nacos/naming/misc/SwitchTest.java create mode 100644 naming/src/test/java/com/alibaba/nacos/naming/raft/RaftStoreTest.java create mode 100644 naming/src/test/java/com/alibaba/nacos/naming/web/APICommandsTest.java create mode 100644 pom.xml create mode 100644 style/codeStyle.md create mode 100644 test/it_test.log create mode 100644 test/pom.xml create mode 100644 test/src/main/java/com/alibaba/nacos/test/App.java create mode 100644 test/src/test/java/com/alibaba/nacos/test/AppTest.java create mode 100644 test/src/test/java/com/alibaba/nacos/test/config/ConfigAPI_ITCase.java create mode 100644 test/src/test/java/com/alibaba/nacos/test/naming/DeregisterInstance_ITCase.java create mode 100644 test/src/test/java/com/alibaba/nacos/test/naming/NamingBase.java create mode 100644 test/src/test/java/com/alibaba/nacos/test/naming/Params.java create mode 100644 test/src/test/java/com/alibaba/nacos/test/naming/RandomUtils.java create mode 100644 test/src/test/java/com/alibaba/nacos/test/naming/RegisterInstance_ITCase.java create mode 100644 test/src/test/java/com/alibaba/nacos/test/naming/RestAPI_ITCase.java create mode 100644 test/src/test/java/com/alibaba/nacos/test/naming/SelectInstances_ITCase.java create mode 100644 test/src/test/java/com/alibaba/nacos/test/naming/SelectOneHealthyInstance_ITCase.java create mode 100644 test/src/test/java/com/alibaba/nacos/test/naming/Starter_ITCase.java create mode 100644 test/src/test/java/com/alibaba/nacos/test/naming/SubscribeCluster_ITCase.java create mode 100644 test/src/test/java/com/alibaba/nacos/test/naming/Subscribe_ITCase.java create mode 100644 test/src/test/java/com/alibaba/nacos/test/naming/Unsubscribe_ITCase.java create mode 100644 test/src/test/java/com/alibaba/nacos/test/smoke/nacosSmoke_ITCase.java create mode 100644 test/src/test/resources/application.properties create mode 100644 test/src/test/resources/logback-test.xml create mode 100644 test/src/test/resources/schema.sql 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 0000000000000000000000000000000000000000..7a4d5452f1084edc18e2d5956d6d243489b3464c GIT binary patch literal 35417 zcmY(rcRUsD`#*jk^VoYk_NGF~-dnO)nUS=J?5vZKEg3~waU?S#WS$5mo3ck_WriHv z_txwE`8^)LKRjN&s&n1fb=}u}jpy||MH?AtQ;@Qf0sts%>AnpeM5s4&%zeD^w zR}%h*#9il_Cjex$XFrI6$>1XZxPY#Ps+r%|>ZEmm^=Q!IKBurDpWWk+E)2##9}NsD zKDw@Tdeu3pHN4akg7`p0FN>Kvu+j|9)x zM83MSVK4T2WfXalYJ3m+B05s#+|y=9FC5L|VFi9%KRop3P;|C_6La+7#=F)Bs0B(Y zDnh6pKwmwb36ZsVR^>MvQlxNX=-DyX*V++cn6$vL|G;*8rD{54FJPf}x#@%Kbp8QA z>mGhNJ>1sR6%{-wsMz%+?X$|$A%!+JT&>G7mpFZMd$=>zo^ zAAn8H+Bi~pFsyHl8*zMBfpM01{r=O){_sn-=h313LbmqWcowF50fV#SxlarLGcmWE zk+^Y2iB6Gm^hH)n@~!J7j&IZ~(2h2gn+xt=6L`gzu*-92)AtY0wBo`a(?*|6!A zC0{B=TVNwwazMeC?_g8eKwN|KbJ@bscaJYqi9ewN8KB+m94eHcDsjE3)E8_7+ zG}9s(!7u(1*VoVW)-hO-+*xeA|Br@o-r!xILM5Q^+xK*_wPLGpW+}+#IsLVahLACx zoxNVxipH50py0+bl_pgjS2nh~;+AIZTUM!9)E4$4tRORNGniAdTRG{iN zJUuw#%u<4-8F3U1x)6ZKz#SQJd)cm27K-mp8C$0mG8)Qjr}ZHfnaM*-O@7fHbm-TL&rcQh zm5A_EqBxogBbMm06hdT#r|w9tC|Jx)P?B>Wj3WCH@*A%zFy{t$FUaqmn|hYg?+d7l z3{PTPD-sNk-|AdflBV{PF;O_IA!Ea>acpju)z5aqbfr8Pcg_lk7 z7ducY2wZ)Ji?FEr%0on6=zJR%1J7jBH+pwB_Hd8+; zxPAEPzNf-&qryYiAM4t(Cn)4I**h`}^4*Ukk^QYMQX0}f{jaWxUq&e^wvK_UzmF=v!M(9}CqFA+pmO7MCoeW581$4XW%^>dEkj=H0=I z`yWFJ`19eLs1Gpu)nlj`zO?AyIC$4W7~g%No^NRL$fN$vM&C>!ep>4HkYGQr8iz+R zzCC4BYCE5rrWUfLdWL(K6B6+L_ICO1k5lGE+3gupg>6%Gqb^Xh)%<^&JlV68dugg} zQhli4TZpg3B|}W44I}Wqn%Bk0cBx+7mDvIHgLmEj>>2kZ7@g_BtwG!q{1nq^m?b>m za94YPk?ar9|Mpv1SxnUaZ3^e0TO6m|D`oAuW7+Vf=S?U5(k1kIS}DzX^V$oebE&pL`j?$HEwIm3|oIW>@0<)V^p{Puo~t^M9`Q zdo-S1IO1#XDyqDYIp>S8syT3pbnd>R{*;(hkUCfS7>AGSHynGGg6cv2*l6~7e3@8< z+HUVpFVj`|n%9G6eEzGAI#Eg=ef+ez4|MBq-A`Xr>V39>6-%Q?V(VmDf7l@_j;FS> z8Bd?9?NPzMtOb-$o3^+bd+Z76--emda3A<1`yUGuTbS?rZgpW+MW#~?P7&A3RdV~AV@I;A8@=2bH9LP7f9xA2t?P8ILrr{$(J#(4c^bOi9UVCnz7!U8oTeN( z-=wQ;vSw6-Q+gO3;e-v>^f0kd>S*;?!@>m8D(w};(WWbK=VG~>&05t3r+^HO{C2%mJ>pG6q+BE*O) z428$)UX^5}9Gh33bD=bUi^GQyPmTIA&19}ST=+TNCzW`aLJu*cP#p58_N{DwXY2ML zli>efhh->#O=vo^hnW#=+EuoFPSN!wk@|S3+I?)Typ{XltB1u^|D2v&X9sfQ3`}RM z=Vfj1oCfBA9O_AeC5ng;X(Cm*m_Co9|8kGeK>*KAglB^^(Et8BJ8Q&S$+zcOboi3` zG5ga%!`8sYZsmKIxexk>H@=0FkF}ypJX8+tyuKsMAMzoWu$K%3}O&>m2 z_Xc@1Z>qB5L}Hb<-$Uc16}s=Hhf2tP;^OU};-~A@ovk}W*-Bt=({X#|_Op?NsEyK# zA=%VH57)|Fpn4&{b&CEfH6;F7#?MOFWKZLO!I#OFOZW=*9PU5e$iBILEs*ZkA$lFj5ZOxLtef8ojR0`jvH z6+BuM$$4O~cLJLgv#UZm+LgGom-nNJ;HoEHR}3X>pZ4xh|RyBD=~I08m4@ zpA)gaq=i;ukzT$6eij8<&v(|O>OL|YkfODV9jt6GL%L^_ZQ zw0IHl#UyK9eCKYN0sU+0IcBgUf|?h+)HRC-Km(^=Gzelu4j>F|Whr~>dCXL-^peV& z516Rx-kr3a-Co<@f#b-ZvK`gDwH|4h-HRK4i+jBeGq89UG(9SAwDXAlB18+n3R#V? zS#S<4O2X8UZ~(CBJ)?ZX@ z^VUT+`_Jn-)d>YkjOf25tv4NH631IMZ|CUCqTL&z#4^7+r9b%bH{*^J-}6$*JiKax`@W%& z(};(+2^BZ%uuK784g*VF>W*1)`K^Qtd%Hv%cYu4x9Jd^i>C@Tp)EEFbiEgAL!)?5I zW0F%OL8bTnt1+WsGoEB~Y_eXGTLbjS+OG?o;zUFWI|GejsV~+Q8*mxj7qQ7@y1$hw z$6f9}NtbxFTjzK}1TrKa*bT(aG8f!P;d6fD20*PGvRH2nLu*j~wMD_{YK zPNv~$2C__mmDQH-AB|8_I}{MkBL^Uo_{M6{jylv{63!Qkozmv!O@|pq2<9Fs-N`e> zBZ16negH9pM<=OxjJa-U{$#NCofo`Q68;N2*jHwFV8V+&*HWlW=STN>WdLkg!%N}F zLh>Lb#h#&+k3#&0Wm93Q=yV{nNckW>bK_)P=FxnZT>Su|CT(&teG3(zQGpk?UWeHo zaBEf*7AILa*{+sRqy`y2^tqn;-OQOqLIze5RKWcfmp~zR3-<;`rqv$MPZ=zhJ*TkP#u9J8U+LK{MzolRlUgXpF)CZZNofBK<$Q*#$tqGR@|XJ0wlbR z1(TMqj5F>V6Jg6lr~5inAGl-AiF0?<4>W%(^KxLc3qExCGhajXJIU=LB@mc>iP<%I zYP-yw{grx*=P3eQ=+gF3BgXc8R&AFk0caMg_Peve_S2I&4~RxQ=h;@3h~Mx{r=HmQ zg8$%-8o07ZQmZ@0ab^HCGQgS;}cS;q5%J+9Uu|X)iQd9~7d@nhvP|`Wkur z<&<}ck*BqsDCWU2>8gtMbx((}E!*X3XG=NpJGXHDSNA6u!zHjgHpxR)zyHcUqgR*k zyi74Q3dbjZ56-%)#kZKa02pMCJEzzARzqA*!O+3$-gMvdS|{qXd~e;gU-eWXDR|`@ z(7ps8$qF=MT0gao$-TnicP6CdJ4^xwCLZ%d!0%2Lt(9;0r8ofe$#6N5E}4h@kAIc~ zsmI=%HnJU{n2N@Box4)^DiEidY65l}j&?siuYE({-u5@&uKClSb(z?&ByJ`oV3V%n zqoG#!ga;nLVu){A&vNV-nJ*iHuxsNMnkLp0BxJPDH42%+qbu@iDNG`2f0@n zk)!1;_`P2|I;PNj+(DCrk3N;@36j*CRWb$GU&J>>9eK{}oM>pVpesvVi;Ip7Mx2Wf zGIz{Pr1v>G>hbgZ_DjPU-W!p-2Tk|{6b>)O7%JRcxVI;d09kV~xUbhNf*Z~Qkep;3 zy4f)WbCEFqCgy|dxiZ({^UHGIL55|L*ME{)p`^%;no1~Y`U5nr`_H;K;*q+I1R z)yOXSE@U9t2|yY)r$l63O)O@GeT7otuB-KGC%%H~;Q5dZ)wwO4%oIxI+qVZOv8E%& zjv9O=%I~S18+Nbh36IHCU~y9A*2r$S>Ul~6z-40R)dsQWakSv$2iN>g!8aqR#Cy=J z!z~;~xeB>VgltnIH)9N#WQQy`;lszcZoW(iuKed%j zrgZ#DObX&fu!$a5drp^9*>T1mMlpEp)oMx3SU}>0s183=KoTrd&N2G}Apte78aci6JMewHHNhjlEqhk5v4R9kK#M+_ z3&r6#PfQ&9?r0{58_x^0%90L%mM)=l1Jn~-YDaqKU@XP=`4gqJQ9US-LsfD>Y>Bv5(X zPw8N_sulU~^R&8m14Fq`39>Xf+GhCJG&fp|e3-hzbWZmh>;Pk8sV7~(CH;oKB+b(!5qFz@Jh|jfa`qRwbvpDLwt}Eq zrp8G_Tsql~9X!(`)tdYu2gd7U4QrcjJTzq!9yJm{DPNQV&RdOJT!UBeW+XFdXzz2T zJuSvb>h9H42+@>tPZUe*HRE&U1M+-$$tC!GadR4k50!W zDGg{`9de+cKRqhB0&@?xB`Q(tPp{JpdL;s`2?T3>-wjjf5DR_u6!j-OG>dnsMq&a< z!2BUG%NjeHMOe4<`J+ShaLQtuhm=L{h z#gX#>*0oBvMdW~>(MVu2{)Qhw^Qn1Fr=)EBMgVaY{GLip5^-0UdOv?<1?bBKoe!SB-7m&zY5bxv;zX|H0MHtX{Cj3q`Yqb z`|rh0hr+?cr`gxyD1l!T^`s%}Trkl4p;$nXtPz4ReP3N_HXtQ}z^W1~GqWM&K%^_o zT3wZ@Xf?SPj`h15#3JP;RE8v@Gu>ivq0^{8m+3F7W5z%|Ew6K zNXEsp;sFlTsP!76jw(EWe`$vN8!VAR_yW$Z)}l$j&G68~TU^J}@UGlB1Gbsc;f3wJ zt{z&c{CRiCK&|QPjdVQ#40tqf*181CaAW|bVlZhrkecYBb*;vK6(N|l8;I_B*W|_3=x2GmNt?bnovm)F8@!@F_E2p4NBy-lfnVi#6j^n);bQSyDR(F!S`Ie^GFYL|s z{`jY-6y2JS~`m-A$nAx-7xNcA5f;W}BKWv}gaa#@fI^F!Y zSabe$9bc;9ep`PAvla*DwEo|*NI1Ya$v1zwq>WB^z4ofQ^j`2<`ssPywbQLvOr?$L zN=Mlt)wv2*%A*VB8Y2mxN@1jar^>Q@YZBAJdU7P>%jkj!vY?x7hVx|Z_+IapgW91?^`w(Ey?)3 zjdDjVdJStN+A(vE-yoLj@uq(+t#3Vi47;EA65S7G9@u{zQu0ic6WYqeNRvR=XW`=+G89k0Gm;@%4E zkK{`Q@D0d6PlWa4>g)I?_K7!-Deo0{QgAjc*POVYEFA{!;r4t_zQ1Ekjik{2bRhDS zNst=D1Q2prj@lis{SO}mujFnAE#T~$hQ9xCtd7h&T<$ff_7q!q!uK5(|1IL)^L^DQ zUJYO0i9egRVue?l5DpeF>MaI55)Ft+z%3yNE)d;z*AOYxV-e4JndWt_DEYPj@!{$H zNAg{QYo*E=)219noWv`q2bK);s_L5Izqxd%SP^mtOKH_Pf28}peA5ia0xsCf-EF;> zv@>BCVxiRE;}PyH;h}u+^+{IVgstb|?NwC=N-Tf)QXBJ;3r{Tqea#6?WQpO?=m4bvw4%1Xq0+X@S zi9nrITJJ7A_P0njXBS`L(O`CInva`0`>7pY@s-AtAJFh2A;MS^cK4N~pe`a6P{yNi z;mKbigS!xEcM3;1_?YMcZLwFTcUe>L3@fW48pS6XIxNv!cXF&u>Qb{WGj$NA?OSx( zc>32;^bKm7`thTFY7B8WXe{^XK2$h-l^VP1cB$@y?Ojo2fsfZuRwC<%r1?rsGiN5n zad}L~+t??Sqw$J&lYSE{fZy~XKZ&MLe#3> z9ew${*RA~0djEv8`#Z8P61Tg!IDWQETUqPvy`XoHvPGgAz9FPju(m^uc=USWnb+UR z)Y#aKG8=u8zR2OAKPEMYv-#I?ZC6B-vXZM@52%qPw${J@789QALx^A!x8<2>;TZ<| zji<=|*nGai9*pc+dHc+O59g5fMm_$}0VuQkLf5&b{fLh{P#0dK@?Y@q;pvm3qbFO5 zKfUL*iFj@N6Hhis27JHojJ^oDYIsSz|FOu{P z;1cZ`{9%|U>u@uE=5X^$V=&El73tGp$6fv)xV$MV-)2`qLfh|1#U!)B_ODP!`0-x^ z%W72Z0-<2K)5OQafm7dKD@j=a+hm^0uk0O$_p2J=!K-V%-Ijc|G~DXtYX!)GOl>qr z6-w6qFeo&zovT6R;v>T6>)g- z3hZHo;mOgIJ&gRNrX=@#+WPmZU5)W{*ylLVd7BMK3rq_|4}u@p_qeQ0EiSO(l%%s` zPywHOwlxyh#qf3hqBLKFNt^5zB3_tN1YY0Fy+EY zGPq&%%t0fGRYJ9n4%yeba(boCv1R-Gy!-gNHpyVngI?Ib+*O6SJY^J>Y-Z5)9jLe? z`(ZB%XAB2_f|uDlpC(&&71;Y;&3zi_b}`f=iGj5nLTHSe(QpCZypFWU(8H4 z$6MZw<_cOs_Jvi5E_cdvHe31Q=Ge#ACtCw9=}*7mu^YYq`|mfxJ2etbc_5Qc4G_RR zOA@`NN0KoSD3=HM1V2{OoF)v5Oc?K3X9B<;e-rC z6mH!!ft{14N>l>xEA~ZFVBs-brTD$$l-MV4oD-Oa&Ys~b;gm`vFTA`LV?9$dFZ4(l zGo%G%r}w6CiXO;)-?W28n>cjm&Ov-V!P4UqNPSqOW4f2ZBUaDZI5Zh_Vxl=v@Htn4 zvjmoC?p|m2lzP;=vw{peaf0wAndLXK!OTFMs8T38Gt(!dn(SCd%fCcdSe5$^%fsnv z%vKN6qMrq#9+$q~2v2>d7t`jZxKaO@M<<&V28S1)tv@`kW@`VlW|kXgrQ(l>i$UWG3=hM z3`nNBTWAlQS?RhK_$;6|c6Q2TVH~G)(f-+v_q-rA){tP?R0YiuK0Lkuxh>UK=~Mrj z=KI5={Km<}3+Aa8SXYc_d^?_y;vUsWKJ^tlbZjD1`9z&N=fSTFs}1ueJ{H-fhA|hp z(Y6k>!<|#Uva0Qbp7pW@9|aISenHU1qVo0`_l#m3>!8Iiw=zw{=(lUH2!-xMbF1L& zv`vnB1kVpJ3IFEW*uSy{4}C8>={gLH`$M!clc&^NVS4djkOQCmpDKhh&Ip0MeP%4$ zXX^Cz;N4q}HCg9giRFq8xx|VJPE?8tPwmgzq?*h?BkNc7s2pvsCc3d8>Tn4@*)_t1 zBdO~RVsEw5->=v1YTAJuG?aSjKfIIPEP~d7T$n%YAV)FJ?C7}70hVr_7GfsXE7wQ& z2iRZZad@15Ad?!D=6BenQ?1h^ShD-(n{9s!C(H2xx@&h`8%lrs9U@`0eZ8$gOLK+Z z*RPhP;~B}onD`bCLlcfBL{94|3kf&m-6$kfoTRE0#d63vvzcmEuN(~ZyjZCAY5F|5 zC^LCHe7*{MFOUWbuCfxncLur;~pt@l`U zB4n-QImR#={Yjf(*@;U9_oE3@ykXOn*5tX*L)PYAcD6`OA11tfh{l=qP@z2)@f-3{|9#Z@gTOD&QNk9&((&hG!^ z3WQMSPVO=N?tIGY{3k7PQd2j6Pni}&H77j{4>r@@U-vxVVAk8Ie_mEPJTI%SYYQl8FYm(G^>;c!%EmQEG zFiA}YQVr^fN&wI6forqp{wz^PP3sfrxbN`o%?>RKfX5`1jpZd>|hRkHP#vCwXU08nshWB-{W`t(tQhH1i>_Bi(4%`04pI7D^`6m zFZlIG?VuO5*WW{!AzgCs0Zi--dAHUR&Wu?cNsWxIJzXZ#mj8#iHzUq1LX6yMg|0uuV$#IUQsg>wGFa|HZnQ8k4PpUeCAHO+B+C*+Sf- zk?H~_$*zF*qUn}*L-&*N{^>spzZL#nc${3{q7W1KLhMwCe0#r8e#?ccW0t!2Jz3B~ z)70LU4Q%Sq;go}J7*3#8R`%*PRpk(MFXWOekbM$Yp;$A1+kD!Kl^=&zUML!NKm3`OJ?KoUqf?dh z?Kt2cV746FTbG{RYkQ9D3mP4~`I&@H?g9WrIk%N;@>*maDb!MnNI33IyK;dO=a(S7NnVwVfU8R{1(w{;z)>lCXtvS4v~G7_P5m0)SJ{prcq2s47^^IZSQ ziN|GIf8m2MV`>VU7#6`N#aBrHni{n`2}g_wNq}Vc)fhL;-pua)edkKZV)raf?Nb!@ z;IE)eDbPbd=NeP`*w+uX3pu)PAHpHb&J(X#4S;Un3(Qx)mP&$+E%duQq>s^|D8 zYWu9jKmbU;t+YMc{l3zXMxxTi|H*I;-EkF>VE6{Bep}c^GKKyBMCm25rXWYi_*>_Dp3DtR9`GQ&O=ZQbC1%o~SawZ!KCZ&L z^;cAYiJrJ|4Xgc6ROsgMQb-?Zzb{S*M& z7{vjiRYF_%okQSh?hPTPEaUtM0;UVscP(Fb|EZ)eA zl&RwwB$JU4EbEOI;R7LqQW$V~hK$<+Kcj>Y04j3ers2`zW&ncY*=I34>ulBY1_j?& zdzPlVqlI=cDQFjCVD0;n7)7X=s8pq(athIFzsCR-@i*B2#?zB+4T(iEfF$LXyTu(l z8$K5V_QF-G+b%m;oI27?c?M>qzTNU5xpmvsn&Cax#bkYAp2LftUG)I_Uk0I{I&@yw z&dwP)TL?T6caLW~Q%MUY0xODdG1$D(S1x2>I$rT$T_8I@uEXIh6o?*=oP3@?JYtb5s2rT5KL`z!zYzb&SzmBbRPA2p)XvlWp z#O2^abV>O;tm1|-%sHp_1WOzc|1_Af!bP9q$+m2Qm;JWB^mSTxOs4uAlmfOX(U-8a)2Mt z3(u?fU?xdK`S=}4-{Zjsd%L$WPLqP%`2?zOjsDq6$uyKJbL}-bVAoK&{l|8rQ+nWz z)OcsBWZZ>ZitHx1v(>v^K>GvkY1}D8p*ST%J8l(XW?b+6j9OR^Q3t_w3Q5nzfBdT{ z?y9;bnE~bq85lqotKS-VheqRj7C*bJs?}^zcGTjB3A-m6zu;Sr7EYUJ@I5g)h0cRn+D&qSfK%iW)~TZX)gbte$V~y;ZVFXd$mg%{}{76}%@V zST04XzEc{QCE?zM{N3HDzYZBu+U7zCG!IPtSKvf=!-WeF1^)bMbNEF6ZAhfHlcsPC zozex<7UOj~ly5Q^eXJy#Fw5LZ5v+h=+9VI*cvHGlt?@e@F!0sgacE2_wX_&|y0*L`(gr zy6APdB*Rj|QFDG^Lhn@v&1X*nbRmyMWGu|vB@gkjSXpHH+_ZhB_DvYlZ$e4P1ANwl z%^@lucLDoRpfj@}Hj}Xs62fiH{0Scxrh#W>`^C)=I=3=|m;12-nU=Yf zXD#Ik>nj{h+#iv1w&`lI0R3yQ5_kl^s3F<^i0et17cv;(SG>jm(`aeEz4;4C;6q4_ zq04E7i$N?w1B`*x9l6x<_EZU;jm|$9*b>sv*>_?YC-0Y-w& zvGbKXkg_4O)wsGP({7Sa0Aij{qnd7u!!0jdw7|~hoAlTnDcrA5{o8>Fq)%;^c41Kz z_vav7{;%|k!{Mc-VD<~Hf8IFG83J7A;H<3!eIJ_yiD>#p^n%X1#jqT}bh+co^U{_G zW=jVikrXVc&ue_sU#e%p^zL8;W|lWkCYPYYxoSVX<1Hga8t81zR9Y}N@1EFnp>QkvEJ<-T>qEwut*<$ByI48fq{(3A z&`N!|MA0`WQ`e+jOB!gZMo~`Re|Vo9P4e^1Urie=d8O57sdqk^ftjkl?=g#qxQ^hz z7UwaGM1B9qgKr5j_|xjIIvfh*xeek7ji|RTaV(BK^NN&|nb(57TPMI?-%vKV%y-U| zMAR)^zC9@A&tA!>gNiG|F^9ZNKQit0LWnPSDjRILo?K_TT27k6ebz*qs|mT-pR zLHbCVv3~Jr;*Ph-&lGRg+rViI)|8t^>@(ZWDA9v`eSFoIj{ld1){Bfo-ggq_JC(80Du3QBu9Z%RqtG}5uVbZ z%G)>p_g2y4zh{ejJ-QWaq=u^>7GVkMxjKFGU2IxOxgc1TCsi#RU+O7o@e#p{sK0rx zDH8wgVtta2AI3tgvd{HCgP!POBsR4T3iOGy$2|cLf=X1l* zKQW=dRBkKik{>-?jc_3w+~td-(7nihZA+SBDq9m14$PnsK68}6J@xwN`{_GUmDu1n zaUFMI0@ONNOf1i#dnKu>NC=iSDf^@~(Cch?Ug#$08*uT-K-?5hWauaYIiI3bgZIuD zHzZ)1I`^qu%h#vDg;89S{4|-@fcgma&y;hYO53Gl@ovwpS-CtL|03h;qHyTj-0sGW zWl}yFiZJsD-Qgi#5{-*KUh&Z@I{H2>s6rgL`KgkOLbu+mGc(#0*?(>cVuHH9Z^NxoV=!|!63*N~?ua!#z;~M-cl541_pRz1@9SH6 z<3V%x*awk&cf-%hu=IJ@*ApKmiJYbb)aC%(DS5F7Ch$M}b?8ts^W1zMS1=tv`%63~ zxq7tFE`Ao%5{QSswolDpE+7^EKtnjgD9H?xFxuGr@qwVL5#GHDyrV#Azb=dW(N2@Z z)$nSO8twf#1nBADqe8M}NV&lTz8JWM?7MRz5I#e31F;5(mN~A!$WDhm@r2o%mIIQ8 zpLL%lcs^fAqHp`!+94RdfAaGKXR-D0Q4+=twx=dMGrHkRyR1`_B?gk z)a*4q;Mi^1_LU0blV7KIcx@sepnZQd3hss4T=AXqkNs+kX^~i<`si$eSbHJ*l2-*s zj*z2HAmV2ZD4A2ESV=1TBh%0-(B7ZnUpgPHNqY9pv)2LE19k=9e>Fy`&C2-3cHKq! zNXz|@fqk>BCNP*)bTzaqKR+>)>qDOGoIp5OkF^6da zhC5_4sL%_>%AM*Mk>9EryvxXs;fbqsiKQiv`y`Zo4YUiNDfq6>?+s%w)4Gz3LB-n(S(6^)?{F6pa){QWO-0~?(?!}6RFD0AP$PZ`y&WYAF z&nfTf)K(LgmxB+|x;$HU?!7f^Eg{R%Gv!cbqd>apEK9Zgor@=nIEoq$yGOq4Bge~&l(}Lnj@}Q zF~YjgmFS0l;)rp~8O zWyxe;A&Xpbq+=HseU?Q&G-?H0pkk^8-@EuAB`xFO#h7GLC~1K08&i+?{_Q*NcMPrz z-wo)&jwN_ouw+Lg=9bPtIi$r2FQN{TEFOpM&x{ftWaSmhT>3F>>LE=A&dZVxot}?L zmMdUxA%sN4dvV1{>qaC6L+MyzQeL=}xp{O{9{#Cs&_nPb%61)kQxP@+hgxVbsZFq? z;^DDo$qac(?wxg*apMfHJek4@c~qrZl#*>BhSJ$x3zp#?GG=7jHL*%P{*n<0s>X*7 zbIrYbEM?=n?d7Z|P8&)FiJ||zXLO$e{(r&39l6q5mSR!Brb=wNZfkP+8ol6sIjk9s zs2YhW)25Y}Jcmgd-6CTmuG?oo(B{TBm?s#?W=kl-Yp>6Dsq#D-&LIGSjC47NQ2+(9 zPu*cBH&b<0w^Yc0wM+2Eb!2}*SIOPEJ(CW*CgY59HcF%=Y;u1ll(Waa+Y2=48oP{p z{q!-3KA3{QD!f*3kmCfY!=mSdlox(A+Gdf@G{B?^`4-w9NhXx~wA=@)tiNFICoFB% zNDwXh+p-k`fArPug+&L?sM5}PW6?eOJKcl7bsQ^Ei z;f11LH{{2@5!P?U&c5N|C@nYh%2~HYb^(>F5~Vb*CCfk;d>*p3Mreq`KLtLQhM3pk zEv{&%p;nGEGk<#grhah`60AJIQt)mp1m0$JDfFmQMEF62mseN78OePfO%c9S2`l!v z4Q;do0g|xZdhKW99`m|h<@oMHT5_=^6FdM1F5CbdpHJa5fo^(x&RM(TkYz3id#^ZY z3XUZ0PjurT4RkktD{Ct!vfgvz{eO7+l#_3_4ty~Fu}pA-0-I#_ z=jZv6ZmXvAf523|0Bj#fJU=tDaM$6(;IP)`s9%6}ch!+l!P)17mRXCzMdvYL1>rn` zAAMAE7H)l0XcgAvtfa7j~69{}sdF!bJy$PptcnE|2(z+Xb~hc!;zP#yE}hjP&IO;0@{4pif?;9 zC*WKEINOjCoih!6?G2H;7c@Bm!c=|A`}Q<32xfYxMe+A+D5N2uPrj2%_)>zzjkYIg ziN0U&(hIV?kne>}mh9e4LCal*lbtx8fEYps();0;RDjkec_#umSq=)KU*4Oj4P`(M z_LuEQ=3@t`bL+=tEZ>HkmU&o;&L|Bo|Ldq<=6!Q-ilensw~jRyzd?f4{qN?`%UeHn z=3myTks{6c3Q=t&uPWR{)V#FekX(&{ucVZc&es|AoGoiu_?e5k7epB4pTopK|2NWp zobi|(APFE^4@*~En6GxN(}|+QG(mU?-}2=Gnz>8iOK#GA{~ku+i#v*t#iIw+SB-~Z z#7v5ERqD9h*TGCd_Z(!X{df0svoWvH>B$Fa1;i~r-Rv7Z6XYB`8otes4Iq1h#6?Ur zn;bF2Tp8dEr{?!gWsIL@e#0ttl}L=4_DqWEe^Rhq)%vdxTsF@z=LKo!klb~gmp?a( z8Z_$U$$S;UpUFs}wQ7}j(^>G7w#eW1CxR&a(FRGM;u%Mfaf&xjIN#>fC@Kl?PF&Zs$jT@sqAJwD$ZT7ofIv_4*_RPAjigG{hBt-=cCAd(sbQ)m!mk zvN!l0bc)p$%o!O8mtIyFPTw#1UUFq@;XUG$sBRq#Y_Lt}*|_VJDZH%ph3XdsIvx?+ zOQrR534_|b|CLwsoxC_(VP45IESR0r_Ndz8o*wBxmv>n7US zgfE>)4VZf@7G2Tn6x0V@QKO}-#w7CHakBdad3mO;RPZ-JhSvYQI?^>k!X}5F9f_O8 zbU*2wg(T0x=Q5J*{4}D^(CKu8Ep6JhCKS!uXt-Ihi76ke-YPZ)qU5uf#;KZwZ24^)%JqJQu=k`7kAc*@f($TsO zm25_01j~f*L&BAVrE0L6^2X3*k%)KOUCFo~_4SJNdYa~S=H~dG&uY@v5PpKs4WLDl zeU0!^LE6w>cDI|#A4p1r!7v@~Wj%1#n#m;}W6jRGoH$w|L#0J%X|ZT@MC6HLC2T=} zJ|0w6E#~utJvVjk;k1rRQgvbYMLoExv*1P~uNVFzv-okglYnFv-Y;uK#(4K2R|~ke z83v!KugYw@FL4$Ad7o)7Za@t53C=`ae>%&TM}C7>gDUUYR8o~Xe+Fv~T^2~LQ6(wi zR;ii3?Az9OyISm3F&h-qawf)gUcLve^H>Y$F!(?~mMT&AKrR)KIwx}}-Va~;IYOQM zx2#d2I0cdR_MDyr1zE`3;+20&kf_$Zkny2|G+5YW2`n?7eF%0RDU#vQ)QTBIj|}$- zF5>(7+PEyTi?cxP>py_Nr>pGFh-HCo^{vce`0rzsaqHkJ4bM!tnS#KEml)x}do&o%-58$^u{bxs z9=ON}v2~OXYef$H-b3WD%5dAHXqk7`MW0^8Zdxy$=;7)J6Zghv`963bJT^c3W+d>@ zA&@Pi1Q7_JPp>LxB*U-kU6E4``Lm-cp12t6T3?Ftd+H_Y)%k8dSDYTYK2(Q|dAVac zL!(UhST%DFUYcwuEVyT`Qxr^$@qd(~^IVgWo?dOo-BtIu5s^!YwuebRIr;;RZS}1& z-tVqOPK1L8A%N&|z@2TU2ecS`EIDCwc{Ws=y&?w<4<47EfvaB=i1ooftYT{r zCEf&A3ZrkxO+P)`-@WfH#t5*^r6D?r>)fGUzzGu0M9psSMWnreUn(SBgjJ)848f&A zRqvrhI`A`rRl6V0=>}6#8;*uQ?RvmOs#m0*m9a^1K0W|FJUTeYT?P&Vp7@0rR}log zR=XDUDRkeoOwHL&v`0v{XnTu5>p6TiBPhlBgp2$$is zLJ#}#A;B-MU6!tR!Y06<)()W#yJGcyHTt-Xgi%!d@`pBWiDyYQ-vQ}g&gv!*tD3w1uf^_h@yRTM zK-F%y1I1v+tZ;o0>x@o1EpTUn(F_qI@_P}qNIyl z-gM`o#w1}ghYw@RHFy;=?45x)xcrs;>29C6<@<+*a@5tVvWX$^o7Z$h_V+#GHirF6 z;6>dvBAW5K#Z8BZ&KJP0K51VaVxhOLbSrxVs;ax)yg=~nV-8i=^+P*zHAtSc%|B8AGps2quZg}Z#q)X`r1u5wUL8ZHqkY)uW z7m)4-NdZwnkWPW61?iA(rBlGAdG7xH&-1>{?2O}#=icwR=brqWbDtCf$WAKS=ORx_ zqSgYtmAgaGw_>Rrscd<0VxX)AKGEO(kxGO#p!IATptL3E^}y8yfwxK08EF0rf#woC z41B1-?^To&o#w1bF!*1U)oqB*y*ohgAMtX_L*SUbZ~76wL{)^8B`HDKE1)lDGIN>e z;o5G`j_eI&ZpeXV;o6BVHZ4OzkI>L$c&MPPNXNXDoV~g22x%s8XZgU}IK*c^6ezH0 z!Q?dT@n1T!8n|TckJ=Io0>o zu;jEqp$9T4L?C_7MX`MVm~Rp5qv7DG&9q4ae#09*fo^)b3fEC33uiWVrP%O)sIW#r z54w?d#0Y|Q*el?XI=l?&%$ZFaG2M}&(wmDr#FB&33HM})()6(=&3?kJYUA0fY>od<%a1qq0P^v)N6iJ;MAK0>*HvRX_?x48 z%SsqFMi9SJH#!^Ss2<=|6%q-_jl)P|H^G%^8g#@52MQ*<{2Wa1S^4}3mNBdFAKB## zq;xs>EWIM)p@Lun1MPQ2tOh@|TwTlMOYmfQ0AgTtuuGt(w?nN&oz`n~f80bO0Q?e# zQPHUoO8Zb;^Nr8KI+blkYAV)FOe$*Fw%G5WnHgkkgM{PbYnB7sIROhy2juG5Yz}A( zJJ6XxUlWpKbkt;`Qa2woMsqR4EWJ)c5%NukGq@EABMWbQxC1h9)JaVel)Xs5!N;{} zW!VeqCvvNyEU`}iagUExeGcigzi&j}VF1K%ZAxd%U$tLibb}ng_4CYj)}5K&5h$Fp z{8f%X#u|qlG-rDI7t2y1>4-rBNcmE~ge+oMzfi*Q(ZnDdR26q-`16Ac1n4S2Rsi$H z5IvW(QBtxA2hZ7`V=eakHx*%zyPJh(M@u-Gct`?`IImnaRMu+CWx4ghrE&{)y^C*O zVF)PG!AC(=B0e^d^d%%?kErJ7Io838DMs>jT&(|DtlpO&(ThY}Q!W0+cb$QHrTrD@ z&?%bB{BLn{5dhg4BYc>0efUFk84bmHI=9ywgZ0?%WjV5@S$ZT5r=7`sNrQ$cL; z2})F**9rWi8_Gma$gG0ZrT-C~c~2{3XSeQyAyOYe6?3HUnj!;>LP5=41+A4m1p41G zJtt;X*k`I^FG4pfeT9lipD+jZ`&AAHCSclrDJe$egqbGM$*A_Cp=9@XSKewcnWEl;2NPLASv^;D4J_j4xBsY)c{2`OPamxEu*{ z)Iq%vn~jqar?5dm5oeW=7JCZR*H5I(=lDz!vSvzpPWe?#I;OMsCO7o5iCd)0WsTy3 zqII}~!IR0%E`mLf74Rf^hz#H82oBkocfP*>?KzC}WFKTKMzF^ch>N@5ikM$Mlocn= z#AT&2%ph(0H0jtN0W4#)#I5#Vf*<=!tdfMgLTj*Fr_dJG9l|uXKH*wF%41-f{#Pdg zHeNOI@H2>?YN_G%>>%mRT@fJ9or;3p4MDl;=KECK$9{N|%tT)aBn`D1)MhnW(fY(Y zL1N%svpnmA%x&~vjkh^DSkJMtQR(C(<6jjgi>G!{*8>Q@BMy{NqU2)U{EkbJO2KL&Hq2bumJ(nM zIj_n$PHB$^cfdQj>9_M4+KeH+K-MCL>33;?I@JV8v6)_a1jP9{95BbRXpSN!h0l<} zo2gs-_1mTYSGnK}ojG`8N|*Bb;G7h$%KKa?c3fm##T#yJjlNB^0C~Wz@&cfyX+xlJ zZMr{F4NIUUX0{0b?n&zu5+d#KNJo0UbmTjxK+9XTRYEyv?DWfMiCL2x0CiKMUVTXM zd$wDG37;prhW-5J+Acz=I$!#w3I*yJGy0C((r`wQ4?Kh)EOdpfz-Pmdugfo)RvgB1h?t<9=RjskhFK|Yg>6jzxK3`;+ zEi`av|Ks+pSN=?~k|T z{7u>2?78K6gn0DKD)d;GKAj_2upuGpy9EtliYELqCfeCjzXnM$^BfIuo;+h?E#p&2 zf{)%}dX-C{N&HIK`B! z3dr!{_$Q>>?@;L*_j=fxPCT2Ynuk`KxECr0J+af3tVb z*>NUN@HQ!^{OL)$!%&oI-uVrc0W&EP zISV(p@~8&xs1NV8F%6E#`nXh{JoWjamiwSj$9gm7k6P#YxBUErwH)c|&mTVG%Xom* zl^f7*^ZH+?#;x2P&DkF?+-Mttgi6GHMgG`4y^3fADZa?FSEx*zjB+b)2FjIG z^=7St3@0$t)X=?(*dca>-P;5P6OYnVVyytTInhXCufxRl(E-m+9i~Mvz`p*t*BJZ4OcRYD+o5COk6Eeh3~14U!G$u5l$Q4kTS}hp#!gG zC%b}shZImg?e*yWh`%b!bZ(-SsKyg7(7#z-)!8p1MB$8rLoN?nR!)p(0a`y(yO`Rv z0b-o@&^2P0Rkfe|wbcGg#?$XWO2!QNUjr;+FF^wzK(Mj&rCBwDkVs{UOs1(R^uglm zuO33`zU(~DU&dx}r*rG^EgBj#DTth0=HF_c@SwA(#G+gsZhXO>NHPI|Vb_og6mNaV zf-G|Z@>ELw%8c9b-YgL+J0Y%1oz2A>N%>G*sh6ZeD%qy@@2N%7h#&fp8>ehpHSH2z zcB{F7EImW(c<_4_ng4I&$)7ZF!W+g%wv19ys&29-5Q}g`S@KA<_k#_1t;G<~M`hU# z%4sNBHZh6e@3?eU*TX~~kc~x5&v(BN*Df=BPG)U{9 zp7Sn;MX|bwWMK6rsx|i5q;$Gh&rg>_>F;ij!A4;hECMo&Iqcxp0_;!l#rSxn7y;}y zk2k=T)$uei$}tw5cHBw{DE<`%THMhAna4R>Ym7KZS_V`p1U%_KE^Z?{$A=}ca&KF< z2hn;8{;rVH79aqE>>Gju3gCx&0>45o4EMiM;~ z?{i0cn^nL*D5eI>#JUyZvfTVf^Y%vOs(<_e+rv#4c_1&pD93VyUt{vi1`F0Q$*j;b zvj|Heq#u(O-;Xd8oQ<)^{`hLu{Fu4btdMOk9eb*#?-P38RN-}!GmSq~>3K}XN4S9jRl$)| z14tSSF!Te43T7^Cix;Lacv3@@{rd1lbSt%Rm~m5?q=5Wx#nm#GmI&OSBuWaaSq!N` zUnqL@jiuXfIS5eEe9b9Bp=KWQ=RER>gt0H76yi%O_zqj1 zPN8k#JQ7{s0eCsjXzne%?uL~dYVaHzcngEDfz@9vl0-is}yp!>dmmj@hl{c zX^8;{0QNJALpi%nR!=GjTb^LIM%r60!8%mdNH9%vz2)7n-sU$l>7uD{Dx*PLT7IUX z>XTlB(1c+Qw!bi0PW-WJa8GR}ll21kK6k8TlEU|&1)FXUSwb>>`_9@}^Y|nB3I{TY z3z-iiv|RFJo+1g!O*vVUgo!=Pi&-W)Al48M`nOl6g=yiFcK=}>(w4wPulkg|^du`E z_=4TOPDRFvXOGhaM84LYHdEL_n8f|@%VluZWh3J+@(R122s#!D^Cmh1DI_Ux(l8|O zPr$Kn=pQNZDRmw+sWvG!ym(ui*!NYO6%w8j3<*F~>uGIx-lqToXp;hW^kcRC98U%* zB(Zqge()`qyA6gKF_9d!C*agcF~t+Iqg#aDN1P|SuJ~S+5D`3-Eq(pAcGd@QM)6N6G5Cy(Rvqetaft}91T zDoDjh^qNZ<9|cPm)(&h(R0onxSbkrpc~yL~)Tp*Vt;f&)WeD>|Gau&rZIurnkd5QB z-wY~sdevOCL$8$(yCD*6nDSP1fU61#B|EClv5E$S6|LL-oz?a&O&9TQA&n~!uin+PE8KMQ3qToxMU#^;f zK8k=BP&dXvfRhRv7&5+~TU2}~b{Mqi@tFFA*5voqDQdd4r*DIJR&a)WIg$QP=%Nra zEt~7R-T`7<(<&{)|Qu4zdM-75VC<|i2;t) ztnP8C8Y|=tQVC>`u0rf3au2R4^Y6ETZvOfMOf|3u%H#qm9Mrkiqp!k~eo;aMXfV_S z0^e^}$*uZns3I?t6Sznv0#NwzF9+B6^07(Z>z-PRw z7i0%L)B6{4Mj++YS_#C&rq{aSu&~%u$I9mE{G|z)5F%Q@#wsdA5!?%a{J$bc0+9Er zQ0#xZBc6@RIk=`m#EknkUzItgqBtnSq#hgMO^C%0cOm!afS8R~N-ow1cS{Qr)j3Xt zm6Yn|zh1tCnJDlPX8Q4g*XHos)$kwbb z?y5kO9UK>MQZevzA;P!SXurp5R}iVzk;;EE!tXn`|v|MxhN3LFWSL%dpMbFU&obhXObS{`_< z0# zjx`7dqplbyL6g)VQAlUTL=95Oc=$%ShcNL(6wq1Hw-7-+n+0yn_y(Pwc81Q#B&(!fv* zn<_FCUk`?&v?Qk6E%Y5dDJ(EbMDG zs+#7)EaLI%7u1zEQR&MiN3IP3&vIe%P$j|{dyFgs7%`)PFt8}JXj&NwE>0z^ z!hi;pJxB$XcXpx#?j|45Zf1E9$#gKyF25)RG(vN|M4bhKUrKUeraghdg%bOqb!-^4 z&8YIR3iMSUzcC|S>k&n=7<;VZRq7N6_-hs*YJk37Jt78K{eqhcdCH`qT&B=^#HWpe zUk*$_X;<(R2MXCsmSlCDSa+xE5D$9LjuS*^pQg`D_AP33x4BSUUB-wr* z_*W`YLm&>|+oo{*U0+RrVw4gfHN`Ug3Wxa@+KM$QZi=;~ww4TQY$jhicYK(+Nb9(D zlOBt3*~&iH5((H{9cyV`nrk}KlG>K^0r)p+>@jQI&*0YZfc+UwEBHWSKY$p~ofAa~ zmiyy%KKIuym$TWkP^fr4lOFoE-h8r-^WAAgw&7LD|8_S^QqUi5x(#{$MJq%eBvChz ztjG2LRwS@${fIDDrrIPc2^z0PRn0mg-*4|`n&nw(P)W{n(=r#wkl>&yT^3lo^%A!y z7_v0K)mfHb4QV^t8YM0^-oE+Vwjv~TaQix3%VP0w$L=o$7{h1aoWKl#E=L-K`a-=1 zLcoHMd|pqz!Z2yrj(Zg|z;oLm+WWztO~Je%(cGK0{wM4~7w#aF(=Y% zROL6{TH=ctqac!eUs3ugBv1mv;{OioX<;Qb{7WA^T=dC6YO_^y*W9*f^y%r!e#KBwZNH{uY|j8=po!M( z{5KWhuX3>Vh+rmq3Jnr4Ylh_Kec7HGa4&k0OdonCn#%obkDdUrN(BrrHw7z3DvH3r zQ}id;4FZHfI7?pd%nZAnU4Yr*nfI^1$#7as0LnwnRbG!|IubZkI%HVCKIR>`uS3vA zXu*qbuoj+Nn!n9n|B}q|1$1zPR~;YP8>L#@01p9YytzvE1eKaf4SA$(54A`uUZTbZ z1L*~d0;>Xu$#49kY|=``^0reFaS>M5|k)&(=XDN=aYSWMOTNOzF4u zET5`lv?jNzTNd~Vd;ski1}jjb$%?~@%A+XjOTL2mk%fGN>fR_>uk^iO{3mlnvi(6m z`ebtDNXM_Da=iHc-$YWdU<%m(!vf@qU4|y;D;OKok`yqa%C^*xVlct)KfCBJf2GC%#w_rw_C-W|8Jf4zbXQiUS8~Lkl&Q#FXf9AY-go z(ahxq=|hcX2bx^77BOCNeA)k9i|itgd*^W)B>pV%$qUxlSzP~wmt-3Z*P;_W zV+SB@Vle0HwAYaYgjp;}P%zh|Jw`(Oon_@Cnfby3%VK^XI^cC#rJ zV&c6>bUFV~0RPjY?E@4s)Pe&TBc*1|bf^kE@7~o+ z&4m%d1tLi4G^T8+twA9Db){-94ug)48)Mj#Rb+it3-vJK4W46U*n8Ep!J9X2ec*@s z30{yQebH1_<;-}naG(Hl8RG|=e#&3^ZhfB6Q*F`m@b)09nW*6!uh)Jhk7w_|KVFOl zVqM7hnI}C&7YKzR$h(2$S6w3p!%d<#S%rMuQXsG-uP}QA4uyNy1+`0J{zO}!%#2oU z*?h>D%DD$I`NQ0+yNWFBm5zFroigkPPGC$eD*BF>w6yBgcvPd8p62)ZC-Pzfh&wM0 z9&+^>pj0fVJeP`$LEs6rV2NTdD)+}=EKiUW1gc?%5z%WG zM+tze&rl@dXZNao95UED1>d65qQVNhmik@wcrgc*(lWu+_^M1n#`;IK(_q$=RNrqi zb1Qw{O@-D`)3Daj9f%D->X{&uO5^fsJKg zB$I$5>&x|A-S@J`SfYZ=^+1PHxn-!WQSu3RBO{r`-10L$i}BdVmMAJd%z=-UiY6x8 zN-mOcv6O3m5w$h?kDBV*Pd|KDW^pOhNL?uXMBhRz(5hC~@A3#orGAO>m%iOxXuHaC zB`aAtoHAeH%DF0E`gQOg_a7eU@Kl8{M40O^x+^B;MQ?Gb>c9BjHYs}Yly_PCB)Gk? z?Mv0qLDcCgM;8dBkNjtv;-*CRMy`iHnEx)}szeVW?@;^mH}UE?iA?)^0{zvOG3LfE zWA-|^CUF;5g-88cw_XxngY}G`e7@V&Go6SieCuaKT)+-s)MZ2JSI0dI5{K?xQ10Qj z#sl!hV5&*CLgOzh#Oyv z*jDtP=;>DAKj_$Lz0MU=)gSS1oq6=Uv0KJxnRE-K1P8@8gt++g$UD{H4cDWogpM|O zEDBl%&EQ2Q*7BDP&fV;iekH1MP2u6neBe_^E#HLC-Q6*;;2siCe zd6Yp)WgUQ(jm#aP%4IJRPMWhne-Wa+(yA+ZF4=WT&MfmnAmmJEH)5v)DCMU9fcfw2 zvTIAupN|i{)F%>p-ZefocHZ&?5jXrj$f`a-X>nLAvQ_{>lg~V#{V1F083tQtpOfMe zFH8Y=Q;L?@u(e@D?h0pp{nB)0dwK8YzH8gcd)KD_ ziKXc6m0x4dPrY8$6EEU zWt$JUd+7Nw2~_)iz-ua4#Pvf359n(CLUhQ=2jiBc3d2NYwE7#<4W=Cj>$GaNU7TfO zOejl**moYNc1WG(E)Pzw>{P3qVQU5vSf_|B#5G=BbWi9A3=K*z+*LNLcWM2hO?#i&H%1bHC_fuYrXD*%;Q{BqD({xpz zh%QcJypO=~dUUXH=IuJHFsfYt#p?$smyuue-Y}2Y&D@&}Z9OUa;(03^XtETH;eo^bu0!k~6tKdj4Lmenx6R`0FQmWw<+oWw@t_^n zVDsAZsAE@hb>=PHTzWBol6G$9p=#U1prHF1L?*;Vk?bt+OWw}O-kJF_80e=~=CF=Z zffLdEkKy7SBK-P0ftdvJ_kVqS|2&3ucqhy29lj_W9$@Afz30bD@3^35ni#=<3C>lg~1 zL0z@mE1o) z1%BSPPp8H_srkZwPI=DK1_#t)f9e|08j=`ugw={)b3y4Hx zEv=J+@K1Dq@-@}dslq43{>XPN+)|4>{@jSb=lTJtQ~IKIe^!<9xgJ ztwGb|_Z8hwToj6r=oUoViQvmA9Rfn%2yc2NU>Cw1vN!fyfdX}&{bpz}%p-{!wL|ru z2@L6?_rLJx58!=%GU{LXp8lDGBeZH0H?w}0skMB#$r^6EkM=sezQ3yCec?wo|B~tp z;@T8o4VRcF1t1cKSbZ@^(o6P8EPTCwD?6<7t1>H3%b zLNj9}E8_hxR$;%jkgiRv`{Qwic4kqpfL7u-&*`OVm-V~Rb3MuIv5}=zyaWG{KZis% z#?WH(o%T~^7Lbu5(|H&Ojx;`y%o9}oWG8;=pmDANHz6~#OG&+E=VtHs$_s6>!f(7^ zPKInv*R!y^vtsVVXYMJ@Z%*@AFp|YT%$f?X-20un-Y?mEeUq;DFK2NmVLvy=9#&}m ziD%_bOc()?l%GwCN6Va%sX8w0SlC%%|B!6-;idZgZTV@ZG}Z9odH>4Xl5XXl=uGxY z-L)oV_6Kx5k5R7HfL#~YmBGc2XN;xxGW>)-0M|SVwPY7RM7*po49MOfCHoagW!r^!VFb!m2b;|3htWHYI_3Z!#JOOsw)G`7<4gdz zp~QHC?+R|{KZ&3=&eF++`*|+KjiuFmN$Hx=g%Ue5unKpg4lr+ejJ;&tH<{njWpen? z+qbmBF961iDzC^bf`Gf(;`BG11f_)&G*?!=TJ@{2YOdj;2+z{eZfccwUeS&@L1kl* z{CDBK=ts6ie{WzU7TSrRLRA2s zBT*cwB#zi2a49}k6#!6xCvM;DX0V~O4NYzSu@vbrY zHkC{wArtGbh0Dc908q?b4dIdn&t`?9u6Sm-b;}`DN))iVRA`_iV$1evWuXv6T~x47 z*kjfF06LbwK)m!A1+G_K=kbC9ELjCd(4A;er$-tQjK0EoLN*Pm6C^Q0vDUn*00$N6 z@aYWW7yNR&?jFkm^jH%to1R&X6VZ(Fb0 zg{0r=Ero7qyJBH$mp7&z&MvDf{0jF6x~d&{OZ!jlL|V@|Vi~D{6a0*hZ9!#K80QK0 zSkmjd{alPXn&*aH-g?!xKH8bV zl|S`NE+M!XR}A~uBHcRYJnZ5{38+6Q=?DbG@e6Bh-{6}6hN%8m)uv8*4Mbwo1lprs z22PYwV5}D+lLH1ZkpdtJsn7_B%TSmeXkEI9&A(mNYVy2UjJ!YEWNCX;D%?8v7Ldiz z4sw%L0F`Yd*Nnz(Xd`u}w0u6OWnPZQ2Q~!P#hcQ*vqY(CQnU|nsMwt_!J3E*tcyXW zl*KaV&6a1ZCnPc6uO%DIDV%=?7TEK58M;SICXcA^Hu^I9+WAT=*iF+2V2)qbfC7V6 z-#f!iFge^1#QxSfU;T?f_|Jv^U)~iAOYd8!2cmoe#!JA?45FV_8Ssmr`YW!DoHl26 z@#DJl>x;ULvs%nHD4T`l#2pY)q7xAuVR&?(a$0A#@j7ah!6#p!{W zZf|}qw*AE9mbjM-ay&LW*>NGzhy;3gWEhICbFu}BH8@6#SUwP$fDsKcj>T4#SNpFU zE8P(9k`oe46Q@K64UrW69j)vb`&^Sqsk(nZ$nvfFjNX^S4+>_6SK%`hb&uj}Q3S-% zwf{{b2{uWREuF1}W=U%(KKc$+1mZ>&T)bO9Zxf@%Gypb)j}(WApYz^l zK&CzvhouHEO!~rG1Le*_vp%9z)!RpyYkYqmZV93wrfcJ`EWPQ&dR?? z8IOJr4u91p81;*uWJ0TZY{}+Fwnf26tGCSKt+7563>DPn9*>GUvOtW z)>SFz`S}o|^?d_KBd@uiSF#_A7Ax)qKNx2}{>1ga`|7Oj)qyZf70UQHS{{h#0ECh& z*c?J1uW}SpbZ&ilFUXw=!B*t9r_LYBjA;p%nV1i`vg@zWCi>BUhrX>+?w$9+h*Y@= zB`aTxb1_)+^8<*ANcKY0CRAgsRtB2Sktoh(6Z`4V5)|^vc4J4&KnE^yMoo{o%diW z2#X06vsRwf4INn#004*^li0CKP$-k9J|xH3*`GqIu~yeggU}nEjA(0XxX0oo3TMn; z=Eze+n9fn>g3^8TYQvBQq5fVPxF58KO>5Dg)OAp?bb0jE27kR{Evf@$B~W5T#A^KK zN>D|RF1|tLfNQNjGbCPFDr)V_*QVs)#XN7;OeD1FO)m!ib)_aJgozB{-@jQ|0z^K! z*eYCF{_{Q2`)wF2vQXYoA}R_nLLi?F%TI8ZD%j9Ore- z+Y6hvH9*-^p(>&t!0mz^P1Ujauw7$WjUtciVNS zkU5KMvF(8A*r51HrchNgEcZwzhbr9-TLJMKFiLz{8`pd z1H9e?x&j^ud@TSnfngaHA~8-bkD3zdF{W81xlu9U^PF)&i|}jIVB^hppR<3aNyaV2 zqqiu(yWAoK_v-nu`@{rwI$sppTOg`V-Gs7tWgX-&%i zZa9;o-<|Hqe289;*bXc%{Q>57T|LI(##M@T?%oT{G#1km8kp`=ke`Pn3WL|q#y9(g zKp)buIykZVLbn7Q;X@^E3(r}447wa-MUbPj6F(=V{kbrN+vKcWXvghN%*|~kKgUR? z36^)rE%UcRjm!N`3}Q2wUhFs+k5=)wIvki8R&Zoigh3|@)%4YcnxiD|TS?@X|3@&M?5+Lt6ja8&O5pv# z1BKZpna67m@IXaTSk)82!_YbXA*f0SG_r=TRw7km&*V|1#6^JYXdefDBZz?t~!z417S?&Nrf!m`F?3bL<#y-pf4ViTQT` zNz|n*-0({B05>03LE<$%m*r5nv$@;<%8m&mR16`o?gT~dr_0i0j(od(A#kJ?&A<< zBR|#La{f}b`DIk=tY{T3Kl8k=+PD_}mHBx_T21z`raN$ZXVsS3k*zZDCKznU3dX$(|M^5(^C>}+I=B{j2J)K2zLJn``>+Fh=ct&fiO z3l^PxP){%>VXZk8cI`J|5`f%o4L7~O?JQ5}en8mLmJI2lXm2G5o!k(YCYAGfKkGTL zAU0k+9JOeYpot`yoCbTs-AC_0E5_*d7ucnxt(d?K)$i!UiT{eCJ}zU^l-}N-%u8tC zNzSDm*EihYJClF+m6raB(*})3_y%b@_M_F;bdbkVN2Q=-n8V&G4g^6>Q3Qdfvp(ju z5M`1gG~EBX$9F24M&{q)C)LfDsI`^tUq+@WffRi$Cg4J)JA9|A6$Lh1qu4rux6VPf zTJ@Agav|^M^t@+I03n2$LWRplNM`HH>cR&=`@@mfnv0B=5q-RKXF|8~YoJ;n*0a@? zn!LN>sNH~c-!!FeB2;z%7aD_&`b6!rK3h5X8KF>ADHh%p;7P+>9b;ZnRI?1tp1d7 zPOi_!C$(G^?&6Ln+BPy&HU3P-1*^Q%L*;>POzCU@7KIoHVeZ?RPE;psdYmWBf-f=j z<~){U??!215pKel1L6ptZjJna{e-ZqAvcAPF-wq}I!F{%sT07yc(g*s0eK^RbK=%> zm_&uT_`gkkcFL8TU6xT**dLdioU#~2Yj^>H>S&WGqf&9L9Y)gVgPi1=nV3|%LG-->? zW;VRo4lH7GKKyN>gZ6)wX}f>_+Q zRA115U;;>rQ-!+3?CrGkl@-V^X-v>DW|Pp4k77Y;H#C*k#IyuLm$t2;=NP=Ity`ba z469jp*0arXdA?uj0&)&s8@~bD21=8Q@e0xKYHSU)){5kiRu+Q0(3pX&I7Yve$lr_X zO(zAbNsc>~#ptXvJOy}A+4lfhj->>yZl7qX#dq*MK)Q4?pwD@*Wm}v%O$4-jPRmoC zQKC?LZ2V3wuj-Z9`ow&o$4YFBDSUz;=?2cAdsw?m4J3CdfCVT06MUC*lOY-76ryg^=< zz!4q~BnX5<{yUAicF~D&7t|o;m<}GGXxYLX|0FmUdlUuYXNkC6Yo@+!5A~92Y~4u) zvbvkgWa6x=>lJ#&Ye=O8qLSp|NBUJ+Z!2^N0rhY|{o~?Im(Cle=N&v0Ys_=VC9dO$Y`yXs%_Q)<)lu14LCU0iGTemKONmcKr;=uen>;653Aa*2G{%QC(+>?ytp@9dsw= zG`c(?@HWqnc)>@yyu*;>!o2DuZ)T%sRWV@mO>l%sSovV>?~q=J#JOPm5mHu2pZKri zu4!bc5}^JECNDl1#eEwfk@!d4!5b#4zZSQ}mMfCS{0vLrd+W&ajxSD!&*eOL)WsVv zeJUGNeF!15)_mk`F$L&!lHt)|v3j19yC6%Xn6L1Xx=ZPn1u92 z48({LvZ_?}osw06-z~l0dwi@sN}cFmL(2$=9Mj9iTqZ(4c3R^5LHldj_;y$ zb@1x^`@o)1zYJMH?;8bIVBg zs+$e7JY4lk%901yRmfsTT8iAy`U}$!2-HxS_`sJAXQy#0Pp~UeQe;+tI=JexxJx#) z0PcWcx(>T2V?HpyAh`R*Vydx0*P1(euuz|#yA3(at$tsz#Q)=NS`l~`oY-T)_e%l7 z+7mw3p99)nIA3ALFlyb|beZ0#NO*m&eM59DmB+m}cR3&u=vo8j;=(bH0`JEATJwXZ zUNuSFhHR_6zjam|mHRXNBA~GDvh+n02!~*jebMU4QUfEWJ_^oD{3ULc2a8A2ncL^S z04hvSjkX>LkZ0vum^K_jJR_3B_q0!C%o8`BtUH8-@jeF!}XQ-XY}^hh`79EyZ>q# zcz@T?@X+Ct$hhaqMcV+IGzj%q0ZK(qXhbY|lL2Z@=jAUtaU?yIDRy=-7UN9E;Fa^_ zxZa7=#GA@^F7$$?*Q`8C~(^J!e&}gPFdz1g}HLKgN$; z4@{@6n%6m;J_|c{X0-7*2-+UBh#`K_F*L!zltI$M>@^N2g7{Q}`;@5g;&7JXpmP0~ zJQTXF1PCffOO4_aRLY!oQexc(ibabdp-@1j(Q%fG#o-|s<7 zRP{#RC2PzlnTwKYarcRp7;h9JiT8XY8e!_G=T(Td(a7Zx=1u2S<)iE?EhGIhzDE-Z z!6H4_uLSy)b!quB{XN9S&~opeo|^h7A2gK63VmC~swvs51i!Bjhb@z{@h+cDj~uf? z{Pe&~x6%7cEmiM?^4|8T$>rRUbk7JbBnxrqDm|}LXMYc%4y>xWVMp}QI{j+2yAhlH z4%37XpVdWsRZE`{Iqn#P7G}xB-*iGU8Q^nXf@;yfmpuCGjvW*n`1p%;M3o?WGZ2Gj zJzIgpvbC(2c=j+2#EG=jNN|VT8J%7llrLvZ(jJrQE-rT)^ZqN={T;8OWk_+~=-sgV z&39Bd3SE$&-D``Sa@8XDKIjtTGuIo7eJ3x#>!%KxwKX8yoV!#ci9K=vy6oPv zm{zZM#ArmzCcw%Xm;ew)Fj2;*;3#L^P2=`&T)vl?X$4GWrK8ifJ$kEtPf}4- zrX*byZ^wwbTgWfWav!e_HTvml?1vNh@pmyRh}_#9++NRIk5)=wqXAKX;qgc}3KXU7RUGRys}|@=bH0%lqHXSbaU@MKiN44O!L90ANq?~m=J%d5!y?5MnsE0Dt#k`%wq-PBNx%fIWC2TBpL*@kFVKJqzoTU?<& zw+dWrSZ3TJ$J|0^g1~OiMn)or^yCM%NBniE*Nff*(MT zHURQK{5!fpNzPg?6$H`jBua}+b*u)0b;}*)SXII3Lf*snC%xDAhSV!j%PRphsZ%#Q zj5U*VkmS{-4(&*T18= z%mN3-%dt2{VYdl}VpQjjHh162f;8fH9O6PW2n3>%^0{L&`xUv6LeK-Rt+4mow^82O zWUc(~jihhoy|U0?8hQ4IsfSd1UA;C~#ywea#FCMH&FL-?oBNNIH-WR{H$QPu^_{b{ z*D7$IeLl-<#;10RAPnVh!g5q<1(8%XV+~%H2Y=oJ6u`G)ob!cF%EFFAY2sI_w0o?UsY8j+Zp2ATQ^Mhp5zy#;8uaY8XE&? zjwAe`1S8x86WgQ*guL@v?O%>Lx?a64GD)kmvf8?oOaLS3kt@PNeXd4*byau64m>_B zpeV?OuF2QPot=ZZ@!Q|-@IH>bxQo==hrkx{u)T11`I)k@@0aoJ8=(lzvfqFYy%t(c z*(M*b@9);^?0X666sMXgRf;;_v`X_QcbWd-9SnW-0azb$KC`cCT!bge&WbaCqq~m% zZSbCf91C&-ef}q--rhCpmT=Tc5NaK5SZP5qZm{w@^K`hqgT@d0 + + + 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)); +